Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 47 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -349,7 +349,53 @@ There are some use cases for nested routers:
- Implement modularity
- Apply middlewares globally

See [example](./src/__tests__/Router.test.ts#216).
An example of multi-tenancy:

```typescript
// create main rooter
const rootRouter = new NodeHttpRouter()
// attach some global urls
// rootRouter.addRoute(...)

// create a router used for all handlers
// with tenant information
const tenantRouter = new Router<{
req: ServerRequest
res: ServerResponse
tenant: string
}>()

// connect routers
rootRouter.addRoute({
matcher: new RegExpUrlMatcher<{
tenant: string
url: string
}>([/^\/auth\/realms\/(?<tenant>[^/]+)(?<url>.+)/]),
handler: ({ data, match }) => {
const { req, res } = data
// figure tenant out
const { tenant, url } = match.result.match.groups
// pass the new url down
req.url = url
return tenantRouter.exec({
req,
res,
tenant,
})
},
})

// attach some urls behind tenant
tenantRouter.addRoute({
matcher: new ExactUrlPathnameMatcher(['/myurl']),
handler: ({ data: { tenant }, match: { result: { pathname } } }) => {
// if requested url is `/auth/realms/mytenant/myurl`, then:
// tenant: mytenant
// pathname: /myurl
return `tenant: ${tenant}, url: ${pathname}`
}
})
```

## License

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@bessonovs/node-http-router",
"version": "2.2.0",
"version": "2.3.0",
"description": "Extensible http router for node and micro",
"keywords": [
"router",
Expand Down
57 changes: 57 additions & 0 deletions src/__tests__/Router.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ import {
import {
Test,
} from 'ts-toolbelt'
import {
ServerResponse,
} from 'http'
import {
MatchedHandler,
Router,
Expand Down Expand Up @@ -223,6 +226,60 @@ it('simple non-matching router type', () => {
})
})

it('tenant router example from docs', () => {
// create main rooter
const rootRouter = new NodeHttpRouter()
// attach some global urls
// rootRouter.addRoute(...)

// create a router used for all handlers
// with tenant information
const tenantRouter = new Router<{
req: ServerRequest
res: ServerResponse
tenant: string
}>()

// connect routers
rootRouter.addRoute({
matcher: new RegExpUrlMatcher<{
tenant: string
url: string
}>([/^\/auth\/realms\/(?<tenant>[^/]+)(?<url>.+)/]),
handler: ({ data, match }) => {
const { req, res } = data
// figure tenant out
const { tenant, url } = match.result.match.groups
// pass the new url down
req.url = url
return tenantRouter.exec({
req,
res,
tenant,
})
},
})

// attach some urls behind tenant
tenantRouter.addRoute({
matcher: new ExactUrlPathnameMatcher(['/myurl']),
handler: ({ data: { tenant }, match: { result: { pathname } } }) => {
// if requested url is `/auth/realms/mytenant/myurl`, then:
// tenant: mytenant
// pathname: /myurl
return `tenant: ${tenant}, url: ${pathname}`
},
})

expect(rootRouter.serve(
createRequest({
method: 'GET',
url: '/auth/realms/mytenant/myurl',
}),
createResponse(),
)).toBe('tenant: mytenant, url: /myurl')
})

it('nested router', () => {
const appRouter = new Router<{
tenant: string
Expand Down
21 changes: 11 additions & 10 deletions src/node/NodeHttpRouter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,31 @@ import {
ServerResponse,
} from 'http'
import {
MatchResult,
MatchedResult,
} from '../matchers'
import {
Handler,
Router,
} from '../Router'
import {
ServerRequest,
toServerRequest,
} from './ServerRequest'

interface ServerRequestResponse {
req: ServerRequest
res: ServerResponse
}

export interface NodeHttpRouterParams<MR> {
data: {
req: ServerRequest
res: ServerResponse
}
data: ServerRequestResponse
match: MatchedResult<MR>
}

export class NodeHttpRouter extends Router<{
req: ServerRequest
res: ServerResponse
}> {
constructor() {
super()
export class NodeHttpRouter extends Router<ServerRequestResponse> {
constructor(defaultHandler?: Handler<MatchResult<unknown>, ServerRequestResponse>) {
super(defaultHandler)
this.serve = this.serve.bind(this)
}

Expand Down
16 changes: 16 additions & 0 deletions src/node/__tests__/NodeHttpRouter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ import {
IncomingMessage,
ServerResponse,
} from 'http'
import {
ExactUrlPathnameMatcher,
} from '../../matchers'
import {
NodeHttpRouter,
} from '../NodeHttpRouter'
Expand All @@ -15,3 +18,16 @@ it('missing url in request', () => {
const nodeRouter = new NodeHttpRouter()
expect(() => nodeRouter.serve({ method: 'GET' } as IncomingMessage, {} as ServerResponse)).toThrowError(`request missing 'url'`)
})

it('default handler', () => {
const nodeRouter = new NodeHttpRouter(({ data: { req } }) => {
return `not found url: ${req.url}`
})
nodeRouter.addRoute({
matcher: new ExactUrlPathnameMatcher(['/test']),
handler: () => 'test url',
})

expect(nodeRouter.serve({ method: 'GET', url: '/test' } as IncomingMessage, {} as ServerResponse)).toBe('test url')
expect(nodeRouter.serve({ method: 'GET', url: '/404' } as IncomingMessage, {} as ServerResponse)).toBe('not found url: /404')
})