Route
A web server uses the request's path and HTTP method to find the right resource. This is called "routing".
In Vafast, routes are defined with route configuration objects that include the HTTP method, the path and the handler.
Basic Routing
Defining Routes (Object Literals)
import { Server, defineRoute, defineRoutes } from 'vafast'
const routes = defineRoutes([
defineRoute({
method: 'GET',
path: '/',
handler: () => 'Hello World'
})
])
const server = new Server(routes)
export default { fetch: server.fetch }HTTP Methods
Vafast supports all standard HTTP methods:
import { defineRoute, defineRoutes } from 'vafast'
const routes = defineRoutes([
defineRoute({ method: 'GET', path: '/users', handler: () => 'Get users' }),
defineRoute({ method: 'POST', path: '/users', handler: () => 'Create user' }),
defineRoute({ method: 'PUT', path: '/users/:id', handler: () => 'Update user' }),
defineRoute({ method: 'DELETE', path: '/users/:id', handler: () => 'Delete user' }),
defineRoute({ method: 'PATCH', path: '/users/:id', handler: () => 'Patch user' })
])Path Parameters
Path parameters let you capture dynamic values from the URL:
const routes = defineRoutes([
defineRoute({
method: 'GET',
path: '/users/:id',
handler: ({ params }) => {
return `User ID: ${params.id}`
}
}),
defineRoute({
method: 'GET',
path: '/posts/:postId/comments/:commentId',
handler: ({ params }) => {
return `Post: ${params.postId}, Comment: ${params.commentId}`
}
})
])Query Parameters
Query parameters are accessed through the query object:
import { defineRoute, defineRoutes } from 'vafast'
const routes = defineRoutes([
defineRoute({
method: 'GET',
path: '/search',
handler: ({ query }) => {
const { q, page = '1', limit = '10' } = query
return `Search: ${q}, Page: ${page}, Limit: ${limit}`
}
})
])Request Body
The request body of POST, PUT and PATCH requests is accessed through the body object:
const routes = defineRoutes([
defineRoute({
method: 'POST',
path: '/users',
handler: async ({ body }) => {
return `Created user: ${body.name}`
}
})
])Route Matching Rules
Vafast uses a radix tree for efficient route matching (O(k) time, where k is the number of path segments).
Route Types
// static routes
'/users'
'/api/v1/health'
// dynamic params (:param)
'/users/:id'
'/posts/:postId/comments/:commentId'
// wildcards (* or *name)
'/files/*' // anonymous wildcard, params['*']
'/static/*filepath' // named wildcard, params['filepath']Priority Rules
Static routes > dynamic params > wildcardsRegistration order does not affect priority:
const routes = defineRoutes([
// even if the dynamic route is registered first
defineRoute({
method: 'GET',
path: '/users/:id',
handler: ({ params }) => `User ${params.id}`
}),
// the static route still matches first
defineRoute({
method: 'GET',
path: '/users/admin',
handler: () => 'Admin user'
})
])
// GET /users/admin → 'Admin user' ✅ static wins
// GET /users/123 → 'User 123'Different Param Names at the Same Position
Different routes can use different parameter names at the same position; each route returns the parameter names it defined:
const routes = defineRoutes([
defineRoute({
method: 'PUT',
path: '/sessions/:id',
handler: ({ params }) => params // { id: '123' }
}),
defineRoute({
method: 'GET',
path: '/sessions/:sessionId/messages',
handler: ({ params }) => params // { sessionId: '456' }
})
])
// PUT /sessions/123 → params = { id: '123' }
// GET /sessions/456/messages → params = { sessionId: '456' }TIP
A warning is logged when parameter names conflict (keeping them consistent is recommended), but functionality is unaffected.
Nested Routes
Vafast supports nested routes with two kinds of nodes:
Route Group (No method)
Only provides a path prefix and shared middleware; no handler:
import { defineMiddleware, defineRoute } from 'vafast'
const logMiddleware = defineMiddleware(async (req, next) => {
console.log(req.method, req.url)
return next()
})
defineRoute({
path: '/files',
name: 'Files',
middleware: [logMiddleware],
children: [ /* leaf routes */ ],
})Leaf Route (Has method)
Actually handles the request; use a relative path:
const listHandler = defineRoute({
method: 'GET',
path: '/list',
handler: () => [...],
})
// put it in a route group
defineRoute({
path: '/files',
middleware: [logMiddleware],
children: [listHandler],
})
// actual path: /files/listMulti-Level Nesting
const routes = defineRoutes([
defineRoute({
path: '/api',
children: [
defineRoute({
path: '/v1',
children: [
defineRoute({
method: 'GET',
path: '/users',
handler: () => 'API v1 users'
})
]
}),
defineRoute({
path: '/v2',
children: [
defineRoute({
method: 'GET',
path: '/users',
handler: () => 'API v2 users'
})
]
})
]
})
])For route groups + type wrappers in auth scenarios, see Auth Middleware.
Route Options
Each route can be configured with the following options:
import { defineRoute, defineRoutes, Type } from 'vafast'
const routes = defineRoutes([
defineRoute({
method: 'GET',
path: '/protected/:id',
middleware: [authMiddleware], // route-level middleware
schema: {
params: Type.Object({ id: Type.String() }), // path param validation
query: Type.Object({ page: Type.Number() }) // query param validation
},
handler: ({ params, query }) => ({ id: params.id, page: query.page })
}),
defineRoute({
method: 'POST',
path: '/users',
schema: {
body: Type.Object({ // request body validation
name: Type.String(),
email: Type.String({ format: 'email' })
})
},
handler: ({ body }) => ({ user: body })
})
])Best Practices
1. Use Descriptive Paths
// ✅ Good
path: '/users/:id/profile'
path: '/posts/:postId/comments'
// ❌ Bad
path: '/u/:i'
path: '/p/:p/c'2. Keep the Route Structure Clear
Organize your API with nested routes:
import { defineRoute, defineRoutes } from 'vafast'
const routes = defineRoutes([
// user routes
defineRoute({
path: '/users',
children: [
defineRoute({ method: 'GET', path: '/', handler: () => 'List users' }),
defineRoute({ method: 'POST', path: '/', handler: () => 'Create user' }),
defineRoute({ method: 'GET', path: '/:id', handler: ({ params }) => `User ${params.id}` })
]
}),
// post routes
defineRoute({
path: '/posts',
children: [
defineRoute({ method: 'GET', path: '/', handler: () => 'List posts' }),
defineRoute({ method: 'POST', path: '/', handler: () => 'Create post' })
]
})
])3. Use the Right HTTP Methods
import { defineRoute, defineRoutes } from 'vafast'
const routes = defineRoutes([
defineRoute({ method: 'GET', path: '/users', handler: () => 'Get users' }), // read data
defineRoute({ method: 'POST', path: '/users', handler: () => 'Create user' }), // create data
defineRoute({ method: 'PUT', path: '/users/:id', handler: () => 'Update user' }), // full update
defineRoute({ method: 'PATCH', path: '/users/:id', handler: () => 'Patch user' }), // partial update
defineRoute({ method: 'DELETE', path: '/users/:id', handler: () => null }) // delete (returns 204)
])4. Type-Safe Route Definitions
defineRoutes() preserves literal types automatically for end-to-end type inference:
import { defineRoute, defineRoutes, Type } from 'vafast'
import type { InferEden } from 'vafast-api-client'
// Define and handle routes
const routes = defineRoutes([
defineRoute({
method: 'GET',
path: '/users/:id',
schema: { params: Type.Object({ id: Type.String() }) },
handler: ({ params }) => ({ userId: params.id })
}),
defineRoute({
method: 'POST',
path: '/users',
schema: { body: Type.Object({ name: Type.String() }) },
handler: ({ body }) => ({ name: body.name })
})
])
// ✅ Type inference just works, no `as const` needed!
type Api = InferEden<typeof routes>5. Use Extension Fields
Vafast lets you add arbitrary extension fields to route definitions for webhooks, permissions, billing and more:
import { defineRoute, defineRoutes, getRouteRegistry, defineMiddleware } from 'vafast'
// billing middleware
const billingMiddleware = defineMiddleware(async (req, next) => {
const registry = getRouteRegistry()
const route = registry.get(req.method, new URL(req.url).pathname)
if (route?.billing) {
await chargeUser(req, route.billing)
}
return next()
})
const routes = defineRoutes([
defineRoute({
method: 'POST',
path: '/ai/generate',
name: 'AI generation',
// ✨ Extension field: billing config
billing: { price: 0.01, currency: 'USD', unit: 'request' },
// ✨ Extension field: webhook event
webhook: { eventKey: 'ai.generate' },
// ✨ Extension field: required permission
permission: 'ai.generate',
middleware: [billingMiddleware],
handler: async ({ body }) => {
return await generateAI(body.prompt)
}
})
])For more details, see Routing Guide - Extension Fields.