Skip to main content Link Menu Expand (external link) Document Search Copy Copied

Routing

Table of contents

  1. Methods
  2. Parameters
  3. Request typing
  4. Grouping
  5. Middlewares
    1. Per route
    2. Per group
    3. Globally
  6. Error handlers
  7. Stand-alone usage
    1. Installation
    2. Example server

Methods

import { RequestInterface, ResponseInterface, HttpStatusCode } from '@serverless-framework/core';
// No matter which provider you use, the signature is the same.
import { Api } from '@serverless-framework/<provider>';

const api = new Api({
    base: '/api',
});

api.get('/api/users', (req: RequestInterface, res: ResponseInterface) => 
    res.sendStatus(HttpStatusCode.OK));
api.post('/api/users/:id', (req: RequestInterface, res: ResponseInterface) => 
    res.sendStatus(HttpStatusCode.OK));
api.put('/api/users/:id', (req: RequestInterface, res: ResponseInterface) => 
    res.sendStatus(HttpStatusCode.OK));
api.patch('/api/users/:id', (req: RequestInterface, res: ResponseInterface) => 
    res.sendStatus(HttpStatusCode.OK));
api.delete('/api/users/:id', (req: RequestInterface, res: ResponseInterface) => 
    res.sendStatus(HttpStatusCode.OK));
api.any('/api/users/:id', (req: RequestInterface, res: ResponseInterface) => 
    res.sendStatus(HttpStatusCode.OK));

Parameters

api.get('/users/:id', (req: RequestInterface<{id: string}>, res: ResponseInterface) => {
    req.status(HttpStatusCode.OK).json({
        id: params.id,
    });
});

// Wildcard routes are supported as well:
// url: /files/avatars/1000/avatar.png
api.get('/files/+file', (req: RequestInterface<{file: string}>, res: ResponseInterface) => {
    // file: /avatars/1000/avatar.png
    req.status(HttpStatusCode.OK).json({
        filename: params.file,
    });
});

Request typing

It’s possible to define types for your parameters, query and body.

const handler = (req: RequestInterface<{title: string}, {page: number}, {text: string}>, res) => {
    const title = req.params.title;
    const page = req.query.page;
    const text = req.body.text;
}

Grouping

api.group('/users', (api: Api) => {
    api.get('/:id', (req: RequestInterface<{id: string}>, res: ResponseInterface) => {
        req.status(HttpStatusCode.OK).json({
            id: params.id,
        });
    });
});

// Nested groups
const videos = (api: Api) => {}
const articles = (api: Api) => {}

const content = (api: Api) => {
    api.group('/videos', videos);
    api.group('/articles', articles)
}

api.group('/content', content);

Middlewares

Having middlewares is as easy as adding more handlers to a route.
Middlewares will be executed by the order you define them.

Stopping the chain of execution

The chain of execution will be ended when a response is sent by methods like: sendStatus(), send() or json().

Per route

function authMiddleware(req: RequestInterface, res: ResponseInterface, next?: NextFunction) {
    if (req.hasHeader('Authorization')) {
        res.sendStatus(HttpStatusCode.UNAUTHORIZED);
    }

    // Continue with the next handler
    if (next) next();
}

api.get('/users/:id', authMiddleware, (req: RequestInterface<{id: string}>, res: ResponseInterface) => {
    req.status(HttpStatusCode.OK).json({
        id: params.id,
    });
});

Per group

api.group('/users', (router: Router) => {
    router.get('/:id', (req: RequestInterface<{id: string}>, res: ResponseInterface) => {
        req.status(HttpStatusCode.OK).json({
            id: params.id,
        });
    });
}, authMiddleware, validationErrorMiddleware);

Globally

api.use(authMiddleware, validationErrorMiddleware);

Error handlers

You can define error handlers the same way as middlewares, the signature is slightly different. Also for error handlers, the order is respected.

function validationErrorMiddleware(err: any, req: RequestInterface, res: ResponseInterface, next?: NextFunction) {
    if (e instanceof ValidationError) {
        // parse the validation error and format it to your own standard (or not).
        res.status(HttpStatusCode.BAD_REQUEST).json(err);
    }

    // Continue, this will go the the next error handler, in this case the errorMiddleware
    if (next) next();
});

function errorMiddleware(err: any, req: RequestInterface, res: ResponseInterface, next?: NextFunction) {
    console.error('Something went wrong', err);
    res.status(HttpStatusCode.INTERNAL_SERVER_ERROR).json({
        message: err.message,
    });
});

api.use(validationErrorMiddleware, errorMiddleware);

Stand-alone usage

Installation

npm install @serverless-framework/router

Example server

Below is a simple example that uses Node’s build-in http server.

  
import * as http from 'node:http';
import { IncomingMessage, ServerResponse } from 'node:http';
import { MethodNotAllowedError, RouteNotFoundError, Router } from '@serverless-framework/router';

// Create a custom request that supports route parameters
type Request = {
    params: { [key: string]: string }
} & IncomingMessage;

type Response = {} & ServerResponse;

// Define a handler
type Handler = (req: Request, res: Response) => void;

// Setup the router
const router = new Router<Handler>();

router.get('/blog/:slug/comments', (req, res) => {
    res.setHeader('Content-Type', 'application/json');
    res.write(JSON.stringify({id: req.params.slug}));
    res.end();
});

// Setup the server
http.createServer(async function (req: IncomingMessage, res: ServerResponse) {
    if (!req.url || !req.method) {
        res.statusCode = 404;
        res.end();
        return;
    }

    try {
        const route = router.lookup(req.method, req.url);
        const _req = req as Request;
        // Assign route params to the request
        _req.params = route.params;
    
        const _res = res as Response;
    
        for (const handler of route.handlers) {
            await handler(_req, _res);
        }
    } catch (e) {
        if (e instanceof MethodNotAllowedError) {
            res.statusCode = 405;
            return res.end();
        } else if (e instanceof RouteNotFoundError) {
            res.statusCode = 404;
            return res.end();
        } else {
            res.statusCode = 500;
            return res.end();
        }
    }
}).listen(8080);