Filesystem based router for express.js
import express, { RequestHandler } from 'express'
import { initFsRouting } from '@parallelworks/fsrouter'
import path from 'path'
const port = 3000
const routesPath = path.join(__dirname, '_routes')
export const getFsRouter = async (routesPath: string) => {
const skipMiddleware: RequestHandler = (req, res, next) => next()
const router = await initFsRouting({
ensureAdmin: skipMiddleware,
ensureAuthenticated: skipMiddleware,
routesPath: routesPath,
})
return router
}
export default async function main() {
const router = await getFsRouter(routesPath)
app.use(express.json())
app.get('/healthzz', (req, res) => res.status(200).send())
app.use('/api', router)
app.listen(port, () => {
console.log(`Server listening on port ${port}`)
})
}
In the above example, a router is created from a path _routes
, and the admin and authenticated middleware are effectively disabled by simply calling next()
.
In a more realistic example, you can define custom middleware functions that determine if a request is authenticated or if the user is an admin.
Routes are declared by exporting a function with the name of an HTTP verb from within one of these files, e.g.
export const GET: Endpoint = (req, res) => {}
In some cases, you may want to add a custom middleware for a route handler. You can optionally export an array instead and route handlers will be executed in order, for example:
export const POST: Endpoint = [
(req, res, next) => {
next()
},
(req, res) => {
res.status(200).send()
},
]
- If the file is named
index.ts
then it will be the root route for that path. - If you prefix the filename with a
:
then it will be considered to be an Express URL parameter. - Folders will take priority over files, so if you have a
/api/index.ts
and/api.ts
, theindex.ts
file will be added first; let's make sure we don't do this though! - If you need to override the routing system for some reason, files prefixed with
_
will be ignored. e.g._index.ts
would not be added to the router.
To make a route require admin access, add the following to the endpoint file:
export const ensureAdmin = true
To make a route public, add the following to the endpoint file:
export const guestAccess = true
When you’re using the @parallelworks/fsrouter package, validation is handled with a special export. The full JSON-schema spec is available in these objects, so we can create some advanced validation if necessary.
export const validation = {
GET: {
query: {
...
}
}
} as const
Where the structure of a query object is a valid JSON Schema, e.g. :
{
type: 'object',
properties: {
name: {
type: 'string',
minLength: 1,
description: 'The name of the resource',
},
},
required: ['name'],
additionalProperties: false,
}
Exporting this object will make sure that any requests that reach your route handlers are the shape defined in your JSON Schema. Any requests that don't match this shape will receive an error response automatically.
That means the type of the request can be asserted, which is done like this:
export const GET: Endpoint<{}, {}, {}, FromSchema<typeof validation.GET.query>> = (req, res) => {
...
}
Since URL params are handled by the filename, you can also assert their type as string with the following:
export const GET: Endpoint<{jid: string}, {}, {}, {}> = (req, res) => {
...
}