mapped-routes
v2.0.0
Published
File-system routing, for Express.
Downloads
8
Maintainers
Readme
Mapped Routes
File-system routing for Express, similar to Next.js' API routes, with support for HTTP methods, custom error handling and more.
Table of Contents
Usage
Create a route for /api/users
:
// api/users/index.js
export default function (req, res) {
res.end('Get a list of users')
}
Create a GET and a PATCH route for /api/posts/:id
:
// api/posts/[id].js
export function get(req, res) {
const id = req.params.id
res.end(`Get post ${id}`)
}
// You can also use `return` instead of the Response object.
export function patch(req, res) {
const id = req.params.id
const body = req.body
return `Patch post ${id} with data ${body}`
}
Automatically map the /api
routes to an Express app:
import express from 'express'
import { MappedRoutes } from 'mapped-routes'
const app = express()
// The directory where your routes are
const dir = __dirname + '/api'
// Load the routes as a regular Express Router.
app.use('/api', MappedRoutes(dir))
app.listen(3000)
You can add your test files (*.spec.js
, *.test.js
) next to the route
files, they will automatically be ignored by the router:
// api/posts/[id].spec.js
describe('Posts by id', () => {
it('should do something wholesome', () => {
// Tests here.
})
})
Installation
With Yarn:
yarn add mapped-routes
with npm:
npm install mapped-routes
The library is typed for Typescript and uses Express as peer dependency, make sure you already have Express installed in your project.
Documentation
The full reference is available here.
Creating routes
The path that the routes are mapped with is the same as for Next.js apps:
users/index.js
will match requests to/users
users/[id].js
will match requests to/users/:id
where:id
is a dynamic parameter.posts/[id]/index.js
will match/posts/:id
where:id
is a dynamic parameter.
You can have nested parameters, which means that the file posts/[postId]/comments/[id]/index.js
will match the route /posts/:postId/comments/:id
.
You can name the folder that contains your routes with any name that you wish. Once you created your folder with some routes, you can add it to your Express app like so:
Import the MappedRoutes function:
const { MappedRoutes } = require('mapped-routes')
Generate the Router and add it to your Express app:
// Generate a Router const apiRouter = MappedRoutes(__dirname + '/api') // Add the router to your express app like you normally do app.use(apiRouter)
You can also use a base path for the router:
const authRouter = MappedRoutes(__dirname + '/auth') // Use this router for the /auth path app.use('/auth', authRouter)
MappedRoutes options
The MappedRoutes()
function takes 2 parameters.
The first one is the absolute path to the directory that contains your routes:
MappedRoutes(__dirname + '/path/to/my/routes')
The second one is an object with configuration options for your router:
const options = { // Middlewares to use before the routes of this router middlewares: [ bodyParser.json(), myCustomMiddleware, ], // A function to run when a route throws an error. // Setting this parameter will override Express' default error handler // for this router. errorHandler: (req, res, err) => { console.error(err) res.json({ error: true, data: content }) }, // A function to run when a route successfully executed. // The third argument is the value returned by the route function. interceptor: (req, res, content) => { res.json({ error: false, data: content }) } } const router = MappedRoutes(__dirname + '/api', options)
The options parameter is optional.
Route handlers
To create a route handler in one of your route files, simply export a function:
// api/users/index.js
export default function(req, res) {
res.end('List of users')
}
You can also return a value instead of using the Response
object:
// api/users/index.js
export default function () {
return 'List of users'
}
Async functions work as well:
// api/users/index.js
import { findAllUsers } from 'some-db-helper'
export default async function () {
return await findAllUsers()
}
Handling specific methods
To handle only GET requests, export a function called get
:
// api/users/index.js
export function get() {
return 'List of users'
}
You can also use arrow functions:
// api/users/index.js
export const get = () => {
return 'List of users'
}
Handling GET and POST requests:
// api/users/index.js
export const get = () => {
return 'List of users'
}
export const post = req => {
return `Create a user named: ${req.body.name}`
}
DELETE
is a special case because it is a reserved keyword in JavaScript, so the word del
is used instead of delete
:
// api/users/[id].js
export const del = req => {
return `Delete user ${req.params.id}`
}
Using middlewares
As we saw in the MappedRoutes() Options, you can define middlewares for your router to use, but you can also define middlewares for individual routes.
To use middlewares for specific routes, simply export an array named middlewares
containing a list of middlewares to use:
// api/users/index.js
export const middlewares = [
someMiddleware,
someOtherMiddleware
]
export default function() {
return 'List of users'
}
You can also define middlewares for specific methods only:
Middlewares for a
GET
method:// api/users/index.js export const getMiddlewares = [ someMiddleware, someOhterMiddleware ]
Middlewares for a
POST
method:// api/users/index.js export const postMiddlewares = [ someMiddleware, someOhterMiddleware ]
Middlewares for a
DELETE
method:// api/users/index.js export const delMiddlewares = [ someMiddleware, someOhterMiddleware ]
Middlewares for all methods, and some for specific methods:
// api/users/index.js export const middlewares = [ bodyParser.json(), analyticsMiddleware ] export const getMiddlewares = [ someMiddleware ] export function get() { // bodyParser was executed // analyticsMiddleware was executed // someMiddleware was executed return 'List of users' } export const postMiddlewares = [ authMiddleware, anotherMiddleware ] export function post() { // bodyParser was executed // analyticsMiddleware was executed // authMiddleware was executed // anotherMiddleware was executed return 'User created' }
The middlewares within the mapped routes are executed in that order:
- Middlewares from the
middleware
options inMappedRoutes()
. - Middlewares exported from
export const middlewares = []
in the route files. - Method-specific middlewares.
Using an error handler
You can use a custom error handler to handle errors that occur in your routes. Be aware that providing a custom error handler for your routes will disable Express' default error handler.
The error handler for mapped routes is a simple function that takes 3 arguments:
export function errorHandler(request, response, error) {
// request is the Request object from Express
// response is the Response object from Express
// error is the error that was caught from the route
console.error(error)
response.end('An error occurred.')
}
Using an interceptor
You can create an interceptor for your mapped routes which will be executed whenever a route ran successfully.
The usual use of an interceptor is to format the responses and, if necessary, to log some information. An interceptor is a function that takes 3 arguments:
export function interceptor(request, response, content) {
// request is the Request object from Express
// response is the Response object from Express
// content is the content returned by the route function
console.log('Route executed successfully:', request.url)
response.json({
error: false,
data: content
})
}
Note that in order to receive content
in your interceptor, your routes need to return some value.
Using with Typescript
MappedRoutes is written in Typescript, so the library comes with type declarations and documentation.
Some extra types are exported to help you type your routes, such as RouteHandler
:
import { RouteHandler } from 'mapped-routes'
// req and res are automatically typed!
export const get: RouteHandler = (req, res) => {
return 'My typed and mapped GET route'
}
// Typing the return type
export const post: RouteHandler = (req, res): number => {
return 123
}
// Typing with generics
export const patch = RouteHandler<boolean> = (req, res) => {
return true
}
// Accept Promises as well
export const put = RouteHandler<string> = async (req, res) => {
return 'OwO'
}
You can also use regular functions and Express' types:
import { Request, Response } from 'express'
// Using functions, simply use Express types
export function get(req: Request, res: Response) {
return 'Some value'
}
// Using functions and typing the return type
export function post(req: Request, res: Response): number {
return 123
}
You can type your error handler and interceptor using the ErrorHandler
and Interceptor
types:
import { ErrorHandler, Interceptor } from 'mapped-routes'
// Typed error handler
export const errorHandler: ErrorHandler = (req, res, err) => {
console.error(err)
}
// Typed interceptor
export const interceptor: Interceptor = (req, res, content) => {
console.log(content)
}
Contributions
Contributions are welcome as Issues or PR! Make sure to read the Code of Conduct.