Stone.jsDocs
Paradigm

Routing

Route definitions

A route definition ties a method and a path to a handler. Stone.js gives you a decorator per HTTP verb, a controller decorator for grouping, and an imperative equivalent for each, all producing the same route table.

#Verb decorators

Inside a controller (a class marked with @EventHandler(basePath)), each method binds to a verb and a sub-path. Paths combine: the controller's base path plus the method's path.

app/Tasks.tsdeclarativeimperative
import { EventHandler, Get, Post, Put, Patch, Delete, Any, Match } from '@stone-js/router'
import { IncomingHttpEvent } from '@stone-js/http-core'

@EventHandler('/tasks')
export class TaskController {
  @Get('/')            list ()            { /* GET /tasks */ }
  @Get('/:id')         show (e: IncomingHttpEvent) { /* GET /tasks/:id */ }
  @Post('/')           create (e: IncomingHttpEvent) { /* POST /tasks */ }
  @Put('/:id')         replace (e: IncomingHttpEvent) { /* PUT /tasks/:id */ }
  @Patch('/:id')       update (e: IncomingHttpEvent) { /* PATCH /tasks/:id */ }
  @Delete('/:id')      remove (e: IncomingHttpEvent) { /* DELETE /tasks/:id */ }
  @Any('/search')      search (e: IncomingHttpEvent) { /* any method */ }
  @Match(['GET', 'HEAD'], '/health') health () { /* specific methods */ }
}
import { defineEventHandler, defineRoutes } from '@stone-js/router'

export const routes = defineRoutes([
  [defineEventHandler(TaskController, 'list'),    { path: '/tasks',     method: 'GET' }],
  [defineEventHandler(TaskController, 'show'),    { path: '/tasks/:id', method: 'GET' }],
  [defineEventHandler(TaskController, 'create'),  { path: '/tasks',     method: 'POST' }],
  [defineEventHandler(TaskController, 'replace'), { path: '/tasks/:id', method: 'PUT' }],
  [defineEventHandler(TaskController, 'update'),  { path: '/tasks/:id', method: 'PATCH' }],
  [defineEventHandler(TaskController, 'remove'),  { path: '/tasks/:id', method: 'DELETE' }],
  [defineEventHandler(TaskController, 'search'),  { path: '/search',    methods: ['GET', 'POST'] }]
])

#The available decorators

DecoratorTypeDescription
@Get(path, options?)Bind a GET route.
@Post(path, options?)Bind a POST route.
@Put(path, options?)Bind a PUT route.
@Patch(path, options?)Bind a PATCH route.
@Delete(path, options?)Bind a DELETE route.
@Options(path, options?)Bind an OPTIONS route.
@Any(path, options?)Match every HTTP method.
@Match(methods, path, options?)Match a specific set of methods.
@EventHandler(basePath, options?)Mark a class as a controller with a shared base path.

#Route options

Every verb decorator (and every entry of defineRoutes) accepts the same option object. The most common are below; later pages cover the rest in context.

OptionTypeDescription
namestringA stable name for URL generation and links.
path*string | string[]The path pattern (imperative form; the decorator takes it as its first argument).
method / methodsHttpMethod | HttpMethod[]The verb(s) matched.
middlewareMixedPipe[]Middleware run on the way to this handler.
rulesRecord<string, RegExp | string>Constraints on path parameters.
bindingsRecord<string, Resolver>Resolve a parameter into a model.
defaultsRecord<string, unknown>Default values for optional parameters.
redirectstring | objectRedirect instead of handling.
prefixstring | falseThis route's relation to the router's global prefix: unset inherits it, false escapes it, a string replaces it. Per-route wins, like strict.
response{ type?, status?, headers? }What the route answers with. The handler returns the payload; the framework builds the response.

#Declaring what a route answers with

A route already says what it is: its path, its verb, what it accepts, whether it is protected. response is the other half of that sentence, so the handler can stay about the domain and there is one place to read when you want to know what an endpoint returns.

app/TasksController.tsts
@Post('/tasks', { response: { type: 'json', status: 201 } })
create (event: IncomingHttpEvent): Task {
  return this.tasks.add(event.get('body'))   // the payload, nothing else
}

@Delete('/tasks/:id', { response: { type: 'no-content' } })
remove (event: IncomingHttpEvent): void { this.tasks.remove(event.get('id')) }
keyTypeDefaultDescription
type'json' | 'jsonp' | 'html' | 'text' | 'file' | 'redirect' | 'no-content''json'What kind of answer it is.
statusnumber200The status a successful answer carries. 302 for a redirect, 204 for no content.
headersRecord<string, string>·Headers to send with it.

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)