Router Composition
As your API grows, it becomes important to split routes across multiple files and compose them
together. feTS supports this through the .use() method on the router, allowing you to merge
sub-routers into a parent router.
Basic Usage
You can merge a sub-router into a parent router by calling .use() with the sub-router as the
argument. The sub-router’s routes will be registered at their original paths.
import { createRouter, Response } from 'fets'
export const usersRouter = createRouter()
.route({
path: '/users',
method: 'GET',
schemas: {
responses: {
200: {
type: 'object',
properties: {
users: { type: 'array', items: { type: 'string' } }
},
required: ['users'],
additionalProperties: false
}
}
},
handler: () => Response.json({ users: ['alice', 'bob'] })
})
.route({
path: '/users/:id',
method: 'GET',
handler: request => Response.json({ id: request.params.id })
})import { createServer } from 'node:http'
import { createRouter } from 'fets'
import { postsRouter } from './posts-router'
import { usersRouter } from './users-router'
const app = createRouter().use(usersRouter).use(postsRouter)
createServer(app).listen(3000)Using a Prefix
You can mount a sub-router under a path prefix by passing the prefix as the first argument to
.use(). All routes from the sub-router will have the prefix prepended to their paths.
import { createRouter, Response } from 'fets'
export const usersRouter = createRouter()
.route({
path: '/',
method: 'GET',
handler: () => Response.json({ users: ['alice', 'bob'] })
})
.route({
path: '/:id',
method: 'GET',
handler: request => Response.json({ id: request.params.id })
})import { createServer } from 'node:http'
import { createRouter } from 'fets'
import { usersRouter } from './users-router'
const app = createRouter().use('/users', usersRouter)
// GET /users → usersRouter's '/' handler
// GET /users/:id → usersRouter's '/:id' handler
createServer(app).listen(3000)Sub-Router Base Path
If a sub-router was created with a base option, that base path is also taken into account when
merging:
import { createRouter, Response } from 'fets'
export const usersRouter = createRouter({ base: '/users' }).route({
path: '/:id',
method: 'GET',
handler: request => Response.json({ id: request.params.id })
})import { createRouter } from 'fets'
import { usersRouter } from './users-router'
const app = createRouter().use(usersRouter)
// GET /users/:id → usersRouter's '/:id' handlerComposing Multiple Levels
Routers can be composed transitively — a merged router can itself be merged into another:
import { createRouter, Response } from 'fets'
// Deepest level
const itemsRouter = createRouter().route({
path: '/:id',
method: 'GET',
handler: request => Response.json({ id: request.params.id })
})
// Mid level
const apiRouter = createRouter().use('/items', itemsRouter)
// /items/:id is now registered
// Top level
const app = createRouter().use('/api', apiRouter)
// GET /api/items/:id → itemsRouter's '/:id' handlerFull Example
Here is a complete example showing how to organise a larger API using router composition:
import { createRouter, Response } from 'fets'
export const usersRouter = createRouter().route({
path: '/users',
method: 'GET',
schemas: {
responses: {
200: {
type: 'object',
properties: {
users: { type: 'array', items: { type: 'string' } }
},
required: ['users'],
additionalProperties: false
}
}
},
handler: () => Response.json({ users: ['alice', 'bob'] })
})import { createRouter, Response } from 'fets'
export const postsRouter = createRouter().route({
path: '/posts',
method: 'GET',
schemas: {
responses: {
200: {
type: 'object',
properties: {
posts: { type: 'array', items: { type: 'string' } }
},
required: ['posts'],
additionalProperties: false
}
}
},
handler: () => Response.json({ posts: ['hello world', 'feTS is great'] })
})import { createServer } from 'node:http'
import { createRouter } from 'fets'
import { postsRouter } from './posts-router'
import { usersRouter } from './users-router'
const app = createRouter().use(usersRouter).use(postsRouter)
createServer(app).listen(3000, () => {
console.log('Swagger UI is available at http://localhost:3000/docs')
})Only user-defined routes are merged from sub-routers. Internal routes like the OpenAPI schema
endpoint (/openapi.json) and Swagger UI (/docs) from sub-routers are not propagated. The
parent router has its own OpenAPI document that aggregates all merged routes automatically.