Routing
Pearl's Router gives you a clean, readable API for defining HTTP routes. Every handler receives a single ctx argument — your one-stop access point for reading the request and writing the response.
Basic routes
Methods map directly to HTTP verbs. Pass a path string and a handler function.
import { Router } from '@pearl-framework/pearl'
const router = new Router()
router.get('/posts', async (ctx) => { /* list posts */ })
router.post('/posts', async (ctx) => { /* create post */ })
router.get('/posts/:id', async (ctx) => { /* get one post */ })
router.put('/posts/:id', async (ctx) => { /* replace post */ })
router.patch('/posts/:id', async (ctx) => { /* update post */ })
router.delete('/posts/:id', async (ctx) => { /* delete post */ })Route parameters
Prefix a path segment with : to capture it as a named parameter. Read it with ctx.request.param('name') — params are always strings, so cast to a number if needed.
router.get('/posts/:id', async (ctx) => {
const id = Number(ctx.request.param('id')) // always a string — cast if needed
ctx.response.json({ id })
})
// Multiple params work fine
router.get('/users/:userId/posts/:postId', async (ctx) => {
const userId = ctx.request.param('userId')
const postId = ctx.request.param('postId')
ctx.response.json({ userId, postId })
})Query strings & request body
Read query string values with ctx.request.query(key, default?). Read the parsed JSON body via the ctx.request.body getter — it's a getter, not a method, so no parentheses.
// GET /posts?page=2&limit=10
router.get('/posts', async (ctx) => {
const page = ctx.request.query('page', '1')
const limit = ctx.request.query('limit', '20')
ctx.response.json({ page, limit })
})
// POST /posts { "title": "Hello", "content": "World" }
router.post('/posts', async (ctx) => {
const { title, content } = ctx.request.body as { title: string; content: string }
ctx.response.created({ title, content })
})Route middleware
Pass an array of middleware functions as the third argument to protect a route. They run in order before your handler. The built-in Authenticate() middleware checks for a valid Bearer token and rejects the request with a 401 if it's missing or invalid.
import { Authenticate, AuthManager } from '@pearl-framework/pearl'
const auth = app.container.make(AuthManager)
const guard = [Authenticate(auth)] // define once, reuse anywhere
// These routes require a valid Bearer token
router.post('/posts', createPost, guard)
router.delete('/posts/:id', deletePost, guard)
// On a guarded route, ctx.get('auth.user') returns the authenticated user
router.get('/me', async (ctx) => {
ctx.response.json(ctx.get('auth.user'))
}, guard)Route groups
Group routes under a shared prefix to keep your router organised:
router.group('/api/v1', (r) => {
r.group('/posts', (r) => {
r.get('/', listPosts)
r.post('/', createPost, guard)
r.get('/:id', getPost)
})
r.group('/users', (r) => {
r.get('/:id', getUser)
})
})Global middleware
Call kernel.useMiddleware() to run middleware on every request. Register your error handler first so it wraps all subsequent handlers.
await new HttpKernel()
.useMiddleware([ErrorHandlerMiddleware, CorsMiddleware, LoggerMiddleware])
.useRouter(router)
.listen(3000)Response helpers
ctx.response is chainable. These cover the common cases:
ctx.response.json({ data }) // 200 JSON
ctx.response.created({ data }) // 201 Created
ctx.response.noContent() // 204 No Content
ctx.response.status(400).json({ message: 'Bad input' }) // 400
ctx.response.unauthorized() // 401
ctx.response.forbidden() // 403
ctx.response.notFound('Not found') // 404
ctx.response.redirect('/login') // 302 Redirect
ctx.response.status(418).json({ im: 'a teapot' }) // custom statusHttpContext reference
| Property / Method | Type | What it gives you |
|---|---|---|
ctx.request.body | unknown | Parsed JSON request body (getter) |
ctx.request.param(key) | string | URL parameter value e.g. :id |
ctx.request.query(key, default?) | string | Query string value |
ctx.request.header(key) | string | undefined | Incoming request header |
ctx.request.method | string | HTTP method |
ctx.request.url | string | Full request URL |
ctx.request.ip() | string | Client IP address |
ctx.response | Response | Builder for the outgoing response |
ctx.get('auth.user') | AuthUser | undefined | Authenticated user (guarded routes) |