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

swagger-http-router

v4.2.1

Published

A HTTP router based on your Swagger/OpenAPI definition.

Downloads

40

Readme

swagger-http-router

A HTTP router based on your Swagger/OpenAPI definition.

GitHub license Build status Coverage Status NPM version Dependency Status devDependency Status Package Quality Code Climate

Why write code when you have a Swagger/OpenAPI definition?

By taking part of the Swagger/OpenAPI standard and dependency injection patterns, swagger-http-router provides a convenient, highly modular and easily testable REST tool.

Usage

import { constant } from 'knifecycle';
import initDB from './services/db';
import {
  initWepApplication
} from 'swagger-http-router';

import API from './swagger.api.json';
import * as HANDLERS from './handlers';

run();

async function run() {
  try {
    // STEP 1: Spawn a Knifecycle instance and attach
    // it the API definition and its handlers
    const $ = initWepApplication(API, HANDLERS);

    // STEP 2: Register additional services
    // Override the build in `uniqueId` service
    // with the UUID v4 function
    $.register(constant('uniqueId', uuid.v4))
    // Provide the process environment
    .register(constant('ENV', process.env))
    // Register the database initializer
    .register(initDB);

    // STEP 3: Run your app!
    // Run the execution silo that encapsulates the app
    // Note that the `httpServer` and `process` services
    // are injected for their respective side effects:
    // creating the server and managing the process
    // lifecycle
    const { ENV, log, $destroy } = await $.run(['ENV', 'log', 'httpServer', 'process', '$destroy']);

    log('info', `On air 🚀🌕`);

    if(ENV.DRY_RUN) {
      await $destroy();
    }
  } catch(err) {
    console.error('💀 - Cannot launch the process:', err.stack);
    process.exit(1);
  }
)

In order to work, your Swagger definition endpoints must provide an operationId. This is how the router figures out which handler to run. Those ids have to be unique. Here is a sample Swagger definition you could use as is:

// file: ./my-swagger.json
{
  "host": "localhost:1337",
  "basePath": "/v1",
  "schemes": ["http"],
  // (...)
  "paths": {
    "GET": {
      "/users/{userId}": {
        "operationId": "getUser",
        "summary": "Retrieve a user.",
        "produces": [
          "application/json"
        ],
        "parameters": [{
          "in": "path",
          "name": "userId",
          "type": "number",
          "pattern": "^[0-9]+$"
        }, {
          "in": "query",
          "name": "extended",
          "type": "boolean"
        }, {
          "in": "header",
          "name": "Content-Type",
          "type": "string"
        }],
        "responses": {
          "200": {
            "description": "User found",
            "schema": {
              "type": "object",
              "properties": {
                "id": { "type": "number" },
                "name": { "type": "string" }
              }
            }
          },
          "404": {
            "description": "User not found"
          }
        }
      }
    }
  }
}

To bring to the router the logic that each endpoint implements, you have to create handlers for each operationId:

// file: ./handlers.js

// Knifecycle is the dependency injection tool
// we use. It provides decorators to declare
// which dependencies to inject in your handlers
import { initializer } from 'knifecycle/dist/util';

export default initializer(
  {
    name: 'getUser',
    type: 'service',
    inject: ['db'],
  },
  getUser
);

async function getUser({ db }) {
  return async ({ userId }) => {
    const user = await db.query('users', {
      id: userId,
    });

    return {
      status: 200,
      headers: {},
      body: {
        id: userId,
        name: user.name,
      }
    };
  }
}

As you can see, handlers are just asynchronous functions that takes the request parameters in input and provide a JSON serializable response in output.

This router is designed to be used with a DI system and is particularly useful with knifecycle.

That said, you could completely avoid using a DI system by simply using the initializers as functions and handle their initialization manually. See this alternate example.

Goal

This router is just my way to do things. It is nice if you use it and I'd be happy if you improve it.

To be honest, I think this is a better approach but I do not want to spend energy and fight with giants to make this a standard approach. It means that it will probably never be the next hype and if you use it you must feel confident with forking and maintaining it yourself. That said, the code is well documented and not that hard. Also, the handlers you will end with will be easily reusable in any framework with little to no changes.

You may wonder why I found that I'd better write my own router instead of using current solutions like ExpressJS or HapiJS:

  • I want documentation first APIs. No documentation, no web service.
  • I want my code to be clear and descriptive instead of binded to some cryptic middleware or plugin defined elsewhere. Here are some thoughts on middlewares that explain this statement in more depth.
  • I want easily testable and reusable handlers just returning plain JSON. To be able to reuse it in multiple situations: a lambda/serverless back-end, when rendering server side React views or in my GraphQL server resolvers.
  • I prefer functional programming: it just makes my code better. There are too many encapsulated states in existing frameworks. I just want my handlers to be pure and composable. For example, why adding a CORS middleware or plugin when you can just compose handlers?
import { reuseSpecialProps } from 'knifecycle/dist/util';

const CORS = {
  'Access-Control-Allow-Origin': '*',
  'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
  'Access-Control-Allow-Headers': 'Keep-Alive,User-Agent',
};

export function wrapWithCORS(initHandler) {
  // `reuseSpecialProps` create a new initializer
  // with the original initializer properties
  // applyed on it.
  return reuseSpecialProps(
    initHandler,
    initHandlerWithCORS.bind(null, initHandler)
  );
}

// This function is the actual initializer that
// wraps the handler initializer. It is executed
// once at startup.
async function initHandlerWithCORS(initHandler, services) => {
  const handler = await initHandler(services);

  return handleWithCors.bind(null, handler);
}

// And finally this one applies CORS to the
// response
async function handleWithCors(handler, parameters) {
  const response = await handler(parameters);

  return  {
    ...response,
    headers: {
      ...response.headers,
      ...CORS,
    }
  };
}
  • and finally, I want to be able to instrument my code without having to do ugly hacks. This is why DI and Inversion of Control are at the core of my way to handle web services.

You may want to have a look at the architecture notes of this module to better grasp how it is built.

Recommendations

The above usage section shows you a very basic usage of this router. For larger apps:

  • you may want to build you Swagger file to avoid repeating yourself. It won't change anything for swagger-http-router since it just assumes a Swagger file.
  • you will probably end up by automating the handlers loading with a configuration file. At that point, the DI system will become very handy.
  • you will certainly need some more services to make your app work. Please double check if one exists before creating your own. Also, handlers can be reused so feel free to publish yours and add your Swagger path objects to them in order for your users to add them to their own Swagger build.

API

Functions

initErrorHandler(services) ⇒ Promise

Initialize an error handler for the HTTP router

Kind: global function
Returns: Promise - A promise of a function to handle errors

| Param | Type | Description | | --- | --- | --- | | services | Object | The services the server depends on | | [services.ENV] | Object | The services the server depends on | | [services.DEBUG_NODE_ENVS] | Array | The environnement that activate debugging (prints stack trace in HTTP errors responses) | | [services.STRINGIFYERS] | Object | The synchronous body stringifyers |

initErrorHandler~errorHandler(transactionId, responseSpec, err) ⇒ Promise

Handle an HTTP transaction error an map it to a serializable response

Kind: inner method of initErrorHandler
Returns: Promise - A promise resolving when the operation completes

| Param | Type | Description | | --- | --- | --- | | transactionId | String | A raw NodeJS HTTP incoming message | | responseSpec | Object | The response specification | | err | HTTPError | The encountered error |

initWepApplication(API, HANDLERS, [$]) ⇒ Knifecycle

Initialize a web application

Kind: global function
Returns: Knifecycle - The passed in Knifecycle instance or the one created by default.

| Param | Type | Default | Description | | --- | --- | --- | --- | | API | Object | | The Swagger definition of the we application | | HANDLERS | Object | | The handlers for each operations defined by the Swagger definition. | | [$] | Knifecycle | getInstance( | A Knifecycle instance on which to set the application up. |

registerHandlers($, HANDLERS) ⇒ void

Register the handlers hash into the given Knifecycle instance

Kind: global function

| Param | Type | Description | | --- | --- | --- | | $ | Knifecycle | The Knifecycle instance on which to set up the handlers | | HANDLERS | Object | The handlers hash |

initHTTPRouter(services) ⇒ Promise

Initialize an HTTP router

Kind: global function
Returns: Promise - A promise of a function to handle HTTP requests.

| Param | Type | Default | Description | | --- | --- | --- | --- | | services | Object | | The services the server depends on | | services.API | Object | | The Swagger definition of the API | | services.HANDLERS | Object | | The handlers for the operations decribe by the Swagger API definition | | [services.ENV] | Object | | The services the server depends on | | [services.DEBUG_NODE_ENVS] | Array | | The environnement that activate debugging (prints stack trace in HTTP errors responses) | | [services.BUFFER_LIMIT] | String | | The maximum bufferisation before parsing the request body | | [services.PARSERS] | Object | | The synchronous body parsers (for operations that defines a request body schema) | | [services.STRINGIFYERS] | Object | | The synchronous body stringifyers (for operations that defines a response body schema) | | [services.log] | function | noop | A logging function | | services.httpTransaction | function | | A function to create a new HTTP transaction |

initHTTPServer(services) ⇒ Promise

Initialize an HTTP server

Kind: global function
Returns: Promise - A promise of an object with a NodeJS HTTP server in its service property.

| Param | Type | Default | Description | | --- | --- | --- | --- | | services | Object | | The services the server depends on | | services.ENV | Object | | The process environment variables | | services.httpRouter | function | | The function to run with the req/res tuple | | [services.log] | function | noop | A logging function |

initHTTPTransaction(services) ⇒ Promise.<function()>

Instantiate the httpTransaction service

Kind: global function
Returns: Promise.<function()> - A promise of the httpTransaction function

| Param | Type | Default | Description | | --- | --- | --- | --- | | services | Object | | The services to inject | | [services.TIMEOUT] | Number | 30000 | A number indicating how many ms the transaction should take to complete before being cancelled. | | [services.TRANSACTIONS] | Object | {} | A hash of every current transactions | | services.time | function | | A timing function | | services.delay | Object | | A delaying service | | [services.log] | function | | A logging function | | [services.uniqueId] | function | | A function returning unique identifiers |

Example

import { initHTTPTransaction } from 'swagger-http-router';

const httpTransaction = await initHTTPTransaction({
  log: console.log.bind(console),
  time: Date.now.bind(Date),
});

initHTTPTransaction~httpTransaction(req, res) ⇒ Array

Create a new HTTP transaction

Kind: inner method of initHTTPTransaction
Returns: Array - The normalized request and the HTTP transaction created in an array.

| Param | Type | Description | | --- | --- | --- | | req | HTTPRequest | A raw NodeJS HTTP incoming message | | res | HTTPResponse | A raw NodeJS HTTP response |

flattenSwagger(API) ⇒ Object

Flatten the inputed Swagger file object

Kind: global function
Returns: Object - The flattened Swagger definition

| Param | Type | Description | | --- | --- | --- | | API | Object | An Object containing a parser Swagger JSON |

getSwaggerOperations(API) ⇒ Array

Return a Swagger operation in a more convenient way to iterate onto its operations

Kind: global function
Returns: Array - An array of all the Swagger operations

| Param | Type | Description | | --- | --- | --- | | API | Object | The flattened Swagger defition |

Example

getSwaggerOperations(API)
.map((operation) => {
   const { path, method, operationId, parameters } = operation;

  // Do something with that operation
});

API

Functions

initErrorHandler(services) ⇒ Promise

Initialize an error handler for the HTTP router

Kind: global function
Returns: Promise - A promise of a function to handle errors

| Param | Type | Description | | --- | --- | --- | | services | Object | The services the server depends on | | [services.ENV] | Object | The services the server depends on | | [services.DEBUG_NODE_ENVS] | Array | The environnement that activate debugging (prints stack trace in HTTP errors responses) | | [services.STRINGIFYERS] | Object | The synchronous body stringifyers |

initErrorHandler~errorHandler(transactionId, responseSpec, err) ⇒ Promise

Handle an HTTP transaction error and map it to a serializable response

Kind: inner method of initErrorHandler
Returns: Promise - A promise resolving when the operation completes

| Param | Type | Description | | --- | --- | --- | | transactionId | String | A raw NodeJS HTTP incoming message | | responseSpec | Object | The response specification | | err | HTTPError | The encountered error |

initWepApplication(API, HANDLERS, [$]) ⇒ Knifecycle

Initialize a web application

Kind: global function
Returns: Knifecycle - The passed in Knifecycle instance or the one created by default.

| Param | Type | Default | Description | | --- | --- | --- | --- | | API | Object | | The Swagger definition of the we application | | HANDLERS | Object | | The handlers for each operations defined by the Swagger definition. | | [$] | Knifecycle | getInstance( | A Knifecycle instance on which to set the application up. |

initHTTPRouter(services) ⇒ Promise

Initialize an HTTP router

Kind: global function
Returns: Promise - A promise of a function to handle HTTP requests.

| Param | Type | Default | Description | | --- | --- | --- | --- | | services | Object | | The services the server depends on | | services.API | Object | | The Swagger definition of the API | | services.HANDLERS | Object | | The handlers for the operations decribe by the Swagger API definition | | [services.ENV] | Object | | The services the server depends on | | [services.DEBUG_NODE_ENVS] | Array | | The environnement that activate debugging (prints stack trace in HTTP errors responses) | | [services.BUFFER_LIMIT] | String | | The maximum bufferisation before parsing the request body | | [services.PARSERS] | Object | | The synchronous body parsers (for operations that defines a request body schema) | | [services.STRINGIFYERS] | Object | | The synchronous body stringifyers (for operations that defines a response body schema) | | [services.ENCODERS] | Object | | A map of encoder stream constructors | | [services.DECODERS] | Object | | A map of decoder stream constructors | | [services.QUERY_PARSER] | Object | | A query parser with the strict-qs signature | | [services.log] | function | noop | A logging function | | services.httpTransaction | function | | A function to create a new HTTP transaction |

initHTTPServer(services) ⇒ Promise

Initialize an HTTP server

Kind: global function
Returns: Promise - A promise of an object with a NodeJS HTTP server in its service property.

| Param | Type | Default | Description | | --- | --- | --- | --- | | services | Object | | The services the server depends on | | services.ENV | Object | | The process environment variables | | services.HOST | Object | | The server host | | services.PORT | Object | | The server port | | services.MAX_HEADERS_COUNT | Object | | The https://nodejs.org/api/http.html#http_server_maxheaderscount | | services.KEEP_ALIVE_TIMEOUT | Object | | See https://nodejs.org/api/http.html#http_server_keepalivetimeout | | services.MAX_CONNECTIONS | Object | | See https://nodejs.org/api/net.html#net_server_maxconnections | | services.TIMEOUT | Object | | See https://nodejs.org/api/http.html#http_server_timeout | | services.httpRouter | function | | The function to run with the req/res tuple | | [services.log] | function | noop | A logging function |

initHTTPTransaction(services) ⇒ Promise.<function()>

Instantiate the httpTransaction service

Kind: global function
Returns: Promise.<function()> - A promise of the httpTransaction function

| Param | Type | Default | Description | | --- | --- | --- | --- | | services | Object | | The services to inject | | [services.TIMEOUT] | Number | 30000 | A number indicating how many ms the transaction should take to complete before being cancelled. | | [services.TRANSACTIONS] | Object | {} | A hash of every current transactions | | services.time | function | | A timing function | | services.delay | Object | | A delaying service | | [services.log] | function | | A logging function | | [services.uniqueId] | function | | A function returning unique identifiers |

Example

import { initHTTPTransaction } from 'swagger-http-router';

const httpTransaction = await initHTTPTransaction({
  log: console.log.bind(console),
  time: Date.now.bind(Date),
});

initHTTPTransaction~httpTransaction(req, res) ⇒ Array

Create a new HTTP transaction

Kind: inner method of initHTTPTransaction
Returns: Array - The normalized request and the HTTP transaction created in an array.

| Param | Type | Description | | --- | --- | --- | | req | HTTPRequest | A raw NodeJS HTTP incoming message | | res | HTTPResponse | A raw NodeJS HTTP response |

flattenSwagger(API) ⇒ Object

Flatten the inputed Swagger file object

Kind: global function
Returns: Object - The flattened Swagger definition

| Param | Type | Description | | --- | --- | --- | | API | Object | An Object containing a parser Swagger JSON |

getSwaggerOperations(API) ⇒ Array

Return a Swagger operation in a more convenient way to iterate onto its operations

Kind: global function
Returns: Array - An array of all the Swagger operations

| Param | Type | Description | | --- | --- | --- | | API | Object | The flattened Swagger defition |

Example

getSwaggerOperations(API)
.map((operation) => {
   const { path, method, operationId, parameters } = operation;

  // Do something with that operation
});

Authors

License

MIT