Stone.jsDocs
Paradigm

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.

app/Tasks.tsdeclarativeimperative
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.
app/Tasks.tsts
@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:

app/middleware/OrganizationGuard.tsts
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)
  }
}
MethodTypeDescription
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.

Stone.js

Your app exists in every runtime. Until you run it.

An open-source project by Stone Foundation
Created by Mr. Stone (Evens Pierre)