Routing
Route middleware
Middleware is where a route states its guarantees in plain sight: who may call it, what the input must look like, what happens around it. Auth, authorization and validation from the Build section are all just middleware attached here.
#Attaching middleware
Add a middleware array to a route, a controller, or a group. Group middleware runs for every route inside; route middleware runs in addition, after the group's, in the order listed.
import { EventHandler, Get, Post, Delete } from '@stone-js/router'
import { requireAuth, requireScopes } from '@stone-js/auth'
import { authorize } from '@stone-js/authz'
import { validate } from '@stone-js/validation'
@EventHandler('/tasks', { middleware: [requireAuth()] }) // group-wide
export class TaskController {
@Get('/')
list () { /* just requireAuth, from the group */ }
@Post('/', { middleware: [validate({ body: NewTask }), requireScopes('tasks:write')] })
create (event) { /* group + route middleware, in order */ }
@Delete('/:id', { middleware: [authorize('delete', 'Task')] })
remove (event) { /* ... */ }
}import { defineEventHandler, defineRoutes } from '@stone-js/router'
import { requireAuth, requireScopes } from '@stone-js/auth'
import { authorize } from '@stone-js/authz'
import { validate } from '@stone-js/validation'
export const routes = defineRoutes([
[defineEventHandler(TaskController, 'list'),
{ path: '/tasks', method: 'GET', middleware: [requireAuth()] }],
[defineEventHandler(TaskController, 'create'),
{ path: '/tasks', method: 'POST', middleware: [requireAuth(), validate({ body: NewTask }), requireScopes('tasks:write')] }],
[defineEventHandler(TaskController, 'remove'),
{ path: '/tasks/:id', method: 'DELETE', middleware: [requireAuth(), authorize('delete', 'Task')] }]
])#Order and opting out
- Order: group middleware first, then route middleware, top to bottom. Put guards before transforms (authenticate, then validate).
- Opt out: a route in a guarded group can drop an inherited middleware with
excludeMiddleware.
@EventHandler('/tasks', { middleware: [requireAuth()] })
export class TaskController {
// A public endpoint inside an otherwise-guarded controller.
@Get('/public', { excludeMiddleware: [requireAuth] })
public () { /* no auth here */ }
}#Reading a parameter before the route is resolved
A kernel or group middleware runs before routing, so event.getRoute() has nothing to give yet, and even on the router layer the parameters are only bound after the route middleware have run. A guard that needs :orgCode used to do a three-line dance: find the route, bind it, read it. The router does the dance for you now, from any layer:
export class OrganizationGuard {
constructor ({ router, organizations }) {
this.router = router
this.organizations = organizations
}
async handle (event, next) {
const orgCode = await this.router.findParam(event, 'orgCode', '')
if (orgCode !== '' && !await this.organizations.exists(orgCode)) {
throw new NotFoundError('No organization ' + orgCode)
}
return next(event)
}
}| Method | Type | Description |
|---|---|---|
| router.findParam(event, name, fallback?) | Promise<T> | One parameter, from any layer. Answers the fallback when no route matches: the 404 belongs to the router at dispatch, not to whoever peeked. |
| router.getBoundRoute(event) | Promise<Route> | The bound route itself, for reading several things. Throws RouteNotFoundError when nothing matches. |