Mount a Plugin Under /api or /v1
Route Prefixes
Pass a prefix when registering a plugin to namespace every route it declares — perfect for versioning and feature mounting.
What you'll learn
- Pass prefix in the register options
- Nest prefixes naturally
- Combine prefixes with versioning strategies
A prefix option on app.register applies to every route inside that plugin. It compounds with
parent prefixes, so the tree maps cleanly to the URL space.
Basic Mounting
// src/routes/users.ts
export default async function usersRoutes(app) {
app.get('/', listUsers); // -> /api/users
app.get('/:id', getUser); // -> /api/users/:id
app.post('/', createUser); // -> /api/users
} // src/index.ts
import usersRoutes from './routes/users.js';
await app.register(usersRoutes, { prefix: '/api/users' }); The plugin doesn’t know it lives under /api/users — easier to test in isolation and to move
around later.
Nested Prefixes Compound
await app.register(
async (api) => {
await api.register(usersRoutes, { prefix: '/users' });
await api.register(postsRoutes, { prefix: '/posts' });
},
{ prefix: '/api/v1' },
); That yields /api/v1/users and /api/v1/posts. Bump the outer prefix to /api/v2 and every
nested route follows.
Trailing Slash Behavior
By default, prefix: '/api/users' matches both /api/users and /api/users/.... If you
register app.get('/'), it answers at /api/users/ and /api/users — Fastify treats the bare
prefix as equivalent. Override with ignoreTrailingSlash: false if you want to be strict.
Versioning Pattern
Combine a prefix with the route-level constraints: { version: '1.0.0' } option to support
parallel API versions in one process — useful while you migrate clients to a new contract.