npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

mapped-routes

v2.0.0

Published

File-system routing, for Express.

Downloads

8

Readme

Mapped Routes

Version Prettier License

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 in MappedRoutes().
  • 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.