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

node-for-speed

v0.1.5

Published

Easy API endpoint management

Downloads

15

Readme

Node For Speed

Easy API endpoint management

Getting started

npm install node-for-speed

const express = require('express')
const nfs = require('node-for-speed')
const server = express()

// ...

await nfs(server)

/*
api/
├── v1/
│   ├── route/
│   │   ├── get.js  => GET api/v1/route
│   │   └── post.js => POST api/v1/route
│   └── ...
└── ...
 */

Usage

const nfs = require('node-for-speed')
// ...
await nfs(server, config)

// OR

const { NodeForSpeed } = require('node-for-speed')
// ...
await NodeForSpeed.load(server, config)

Where config is an optional parameter to be "assigned" to your main configuration for the given execution.

Configuration

programmatic

NodeForSpeed.config({
  loader: 'express', // optional
  router: 'express', // optional
  paths: [
    'path/to/routes',
    {
      path: 'path/to/other/routes',
      prefix: 'v1' // optional
    }
  ],
  adapter: 'path/to/adapter', // optional
  route: 'path/to/route' // optional
})

All paths passed to the config are relative to your project working directory.

loader (String)

Built in server loader, path to your loader or loader module name. Available loaders: express Default: express

router (String)

Built in router, path to your router or router module name. See Router for more details. Available routers: express Default: null

paths (String | Object | Array)

Paths to directories containing your endpoints as a string, a branch object (cf. example) or an array containing any of the two. Every string will be converted into branch.

// branch
{
  path: 'path/to/your/routes',
  prefix: 'v1' // optional
}

adapter (String)

Path to a custom function, object or Adapter class. See Adapter for more details.

route (String)

Path to a custom Route class. See Route for more details.

.node-for-speed file

If provided as JSON, the .node-for-speed file will be loaded automatically as the node-for-speed module is called.

package.json

If provided, the "node-for-speed" property of your package.json file will be automatically loaded as the node-for-speed module is called. The .node-for-speed file has a higher priority.

as module

The configuration can be auto-loaded from a module on startup using a config property in either of the two previous cases:

{
  config: 'path/to/configuration'
}

Then:

module.exports = {
  /* your config in js */
}

Endpoint

Endpoints are simple modules named after the method they address:

// ./api/v1/anything/get.js

module.exports = {
  path: 'new', // optional
  handler: (request, response) => {
    /* ... */
  }
  // OR
  handler: [
    ...middlewares,
    (request, response) => {
      /* ... */
    }
  ]
}

// with path
// => GET api/v1/anything/new

// without path
// => GET api/v1/anything

Folder names can be overridden using an index.js file (useful for url params):

// ./api/v1/anything/index.js

module.exports = 'something'

// OR

module.exports = {
  path: 'something'
}

// will cause the example above to become
// => GET api/v1/something/new

An endpoint can override a folder name using ~ :

// ./api/v1/anything/get.js

module.exports = {
  path: '~/brand/new',
  handler: (request, response) => {
    /* ... */
  }
}

// => GET api/v1/brand/new

An endpoint url can be set manually as follow:

// ./api/v1/nested/route/with/a/twist/get.js

module.exports = {
  url: 'api/v1/boom',
  handler: (request, response) => {
    /* ... */
  }
}

// => GET api/v1/boom
// instead of
// => GET api/v1/nested/route/with/a/twist

Customization

Loader

A loader defines how a route is mounted on your server (or router) and delegates the task an adapter if provider.

module.exports = (server, route, adapter, router) => {
  /* ... */
}

Router

The Router class allows you to define:

  • how a router is built based on your configuration
  • how a router is attached to your server
  • how a route is mounted
class Router {
  constructor (server, branch) {
    /* ... */
  }

  handler (route) {
    /* ... */
  }
}

Route

The purpose of the Route class is to build your endpoints' path and allow you to extend them as shown in example.

class Route {
  constructor ({
    adapter,  // optional
    key,      // current path section
    filepath, // endpoint location
    prefix,   // optional path prefix
    endpoint, // actual endpoint
    parent,   // parent route
    method,   // rest method
    branch    // branch configuration
  }) {
    /* ... */
  }
}

A route has the following properties:

path

The complete endpoint path.

handler

The endpoint's handler.

key

The name of the folder containing the endpoint.

prefix

The prefix to prepend to the path.

endpoint

The actual endpoint.

parent

The parent route object.

method

The rest method addressed (lowercase).

filepath

The endpoint file location.

Adapter

An adapter customizes the way a route is mounted on your server. It is defined as:

class Adapter {
  before (server, options) {
    /* ... */
  }

  handler (server, route, router) {
    /* ... */
  }

  after (server, options) {
    /* ... */
  }
}

before(server, options) and after(server, options) are optional methods called respectively pre and post loading. In this context options is defined as:

// Given
NodeForSpeed.config(defaults)
// And
NodeForSpeed.load(server, config)
// Then
options = Object.assign({}, defaults, config)

Given the optional nature of before and after, adapters can be expressed under any of the following forms:

// as a function
module.exports = (server, route, router) => { /* ... */ }
// as an object
module.exports = {
  before: (server, options) => { // optional
    /* ... */
  },
  handler: (server, route, router) => {
    /* ... */
  },
  after: (server, options) => { // optional
    /* ... */
  }
}
// as a class extending Adapter
module.exports = class CustomAdapter extends Adapter {
  /* ... */
}

Example

In this example, we will customize node-for-speed to attach middlewares to our endpoints given the following project structure:

project/
├── ...
├── custom/
├── middlewares/
├── routes/
│   ├── admin/
│   │   └── ...
│   ├── private/
│   │   └── ...
│   └── public/
│       └── ...
├── ...
├── index.js
├── package.json
├── routes.js
└── ...

Endpoint

Endpoints will be written under the form:

const middleware = require('path/to/middleware')

module.exports = {
  use: middleware,
  // OR
  use: [ middleware ],
  handler: (request, response) => {
    /* ... */
  }
}

Route

There is no need to extend the Route class in this example. But if, for instance, we wanted to define the middlewares by name rather than by actually attaching them to our endpoints, we would write:

const Route = require('node-for-speed/route')

class MiddlewareRoute extends Route {
  constructor (args) {
    super(args)

    const { endpoint } = args
    const { use } = endpoint
    const middlewares = Array.isArray(use) ? use : [ use ]

    this.use = middlewares.map(this.getMiddleware)
  }

  getMiddleware (name) {
    /* ... */
  }
}

module.exports = MiddlewareRoute

Our endpoints would then become:

module.exports = {
  use: 'middleware',
  // OR
  use: [ 'middleware' ],
  handler: (request, response) => {
    /* ... */
  }
}

Router

As we want apply prefix and middlewares at a branch level, we will have the following Router:

const ExpressRouter = require('node-for-speed/router/express')

class MiddlewareRouter extends ExpressRouter {
  init (server, router, branch = {}) {
    const {
      prefix = '',
      use
    } = branch

    if (use instanceof Function || use instanceof Array) {
      server.use(prefix, use, router)
    }
    else {
      server.use(prefix, router)
    }
  }

  handler (route) {
    const { router } = this
    const { path, method, handler, parent, endpoint } = route
    const middlewares = []

    // get index middlewares
    const indexwares = this.getMiddlewares(parent.endpoint)
    // get the method middlewares
    const methodwares = this.getMiddlewares(endpoint)

    if (indexwares.length) {
      middlewares.push(...indexwares)
    }

    if (methodwares.length) {
      middlewares.push(...methodwares)
    }

    if (middlewares.length) {
      router[ method ](path, middlewares, handler)
    }
    else {
      router[ method ](path, handler)
    }
  }

  getMiddlewares ({ use } = {}) {
    const middlewares = []
    if (use instanceof Function) {
      middlewares.push(use)
    }
    else if (use instanceof Array) {
      middlewares.push(...use)
    }

    return middlewares
  }
}

module.exports = MiddlewareRouter

In the scenario exposed in the Route section, we would have passed route and parent to getMiddlewares rather than the endpoints.

Adapter

Let's add error handlers to our server:

module.exports = {
  after: server => {
    // 404 handler

    server.use((request, response, next) => {
      response.status(404).send('Page not found')
    })

    // error handler

    server.use((err, request, response, next) => {
      /* ... */
      response.status(500).send('Something went wrong...')
    })
  }
}

Configuration

package.json

{
  // ...
  "node-for-speed": { config: "./routes.js" }
  // ...
}

We load the configuration through a module to import middlewares.

routes.js

const isAuthenticated = require('./middlewares/authenticated')
const isAdmin = require('./middlewares/admin')

module.exports = {
  adapter: './custom/adapter',
  router: './custom/router',
  paths: [
    {
      path: './public'
    },
    {
      path: './private',
      use: isAuthenticated
    },
    {
      path: './admin',
      prefix: '/admin',
      use: [ isAuthenticated, isAdmin ]
    }
  ]
}

index.js

const express = require('express')
const nfs = require('node-for-speed')
const app = express()

/* ... */

nfs(app).then(() => app.listen(3000))

/* ... */

Roadmap

In progress

  • code sample
  • document branch as a module
  • document branch handlers (middlewares)
  • openapi path parameters
  • swagger generated API documentation

Planned

  • static endpoints (e.g. routes/404.html)
  • document custom endpoint filenames
  • Built-in loaders: koa, hapi
  • rest file
// ./api/v1/anything/rest.js

module.exports = {
  index (params) { // GET api/v1/anything

  },
  get (id, params) { // GET api/v1/anything/:id

  },
  post (id, data, params) { // POST api/v1/anything/:id

  },
  put (id, data, params) { // PUT api/v1/anything/:id

  },
  patch (id, data, params) { // PATCH api/v1/anything/:id

  },
  delete (id, data, params) { // DELETE api/v1/anything/:id

  },
  procedure (id, data, params) { // POST api/v1/anything/:id/procedure

  }
}