Skip to content

Instantly share code, notes, and snippets.

@herber
Last active April 4, 2021 19:33
Show Gist options
  • Save herber/c60591e5f27b125f70d3d3c99a630a6d to your computer and use it in GitHub Desktop.
Save herber/c60591e5f27b125f70d3d3c99a630a6d to your computer and use it in GitHub Desktop.
A tiny but blazing fast router for Vercel serverless functions.

Vercel Router

A tiny but blazing fast router for Vercel serverless functions.

Usage

import { router } from '...';

let app = router({
  basePath: '/api',
  allowCors: true
});

// Support for path params
app.get('/something/:id', (req, res) => {
  res.send('Something: ' + req.params.id);
});

app.post('/route', (req, res) => {
  res.send('POST /route');
});

app.all('/*', (req, res) => {
  res.status(404).send('Not found');
});

// Let Vercel execute the router
export default app.run;
import { VercelRequest, VercelResponse } from '@vercel/node'
export interface Request extends VercelRequest {
params: { [key: string]: string }
}
export type Handler = (req: Request, res: VercelResponse) => unknown;
interface Route {
match: string;
handler: Handler;
method: string;
}
let match = (path: string, route: Route) => {
let match = path.match(route.match);
if (!match) return false;
return match.groups || {};
};
export let router = ({ basePath, allowCors }: {
basePath?: string,
allowCors?: boolean
}) => {
if (!basePath) basePath = '';
if (basePath.endsWith('/')) basePath = basePath.slice(0, -1);
let routes: Route[]
let addRoute = (method: string, route: string, handler: Handler) => {
routes.push({
match: `^${(basePath + route)
.replace(/(\/?)\*/g, '($1.*)?')
.replace(/\/$/, '')
.replace(/:(\w+)(\?)?(\.)?/g, '$2(?<$1>[^/$3]+)$2$3')
}\/*$`,
handler,
method: method.toUpperCase()
})
};
let run = (baseReq: VercelRequest, res: VercelResponse) => {
if (allowCors) {
res.setHeader('Access-Control-Allow-Credentials', 'true')
res.setHeader('Access-Control-Allow-Origin', '*')
res.setHeader('Access-Control-Allow-Methods', 'GET,OPTIONS,PATCH,DELETE,POST,PUT')
res.setHeader(
'Access-Control-Allow-Headers',
'X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version'
)
if (baseReq.method === 'OPTIONS') {
res.status(200).end('');
return
}
}
let path = new URL(baseReq.url, `http://${baseReq.headers.host}`).pathname;
for (let route of routes) {
if (baseReq.method != route.method && route.method != 'ALL') continue;
let params = match(path, route);
if (params) {
let req: Request = Object.assign(baseReq, { params });
route.handler(req, res);
}
}
}
return {
run,
all: (route: string, handler: Handler) => addRoute('ALL', route, handler),
get: (route: string, handler: Handler) => addRoute('GET', route, handler),
post: (route: string, handler: Handler) => addRoute('POST', route, handler),
put: (route: string, handler: Handler) => addRoute('PUT', route, handler),
patch: (route: string, handler: Handler) => addRoute('PATCH', route, handler),
delete: (route: string, handler: Handler) => addRoute('DELETE', route, handler),
head: (route: string, handler: Handler) => addRoute('HEAD', route, handler),
options: (route: string, handler: Handler) => addRoute('OPTIONS', route, handler),
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment