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

express-primer

v0.2.0

Published

Adds validation and spec generation to express apps

Downloads

10

Readme

express-primer

Express Primer is a more robust base for creating Node APIs, with built-in request (and response) validation, easy route nesting, and automatic spec generation and documentation.

How?

We provide several helper classes that make use of standard technologies like JSON Schema and OpenAPI Spec (formerly known as Swagger Spec). These helper classes create introspection into your API, which naturally and automatically enables features such as validation and documentation.

JSON Schema

JSON Schema is a way to describe the shape and format of a JSON value in the form of a "schema", which is just a JSON object with rules. This schema can then be used to validate JSON against its rules, as well as utilized for documentation via OpenAPI.

Here's an example of how JSON Schemas are used for validation: RunKit.

Here's a list of keywords you can use in a JSON Schema: AJV Docs.

OpenAPI

OpenAPI is a way to describe your API and its various facets, such as endpoints, request/response structure, models, parameters, etc. Like JSON Schemas, it is simply a JSON object. This object can then be ingested to generate documentation, API clients, tests, mocks, etc. Many components within the OpenAPI spec are described by JSON Schemas.

You can learn about OpenAPI here and delve deeper into the spec at its repo.

Installation

npm install express-primer express ajv --save

The express and ajv packages are peer dependencies, so they must be installed for express-primer to work.

Usage

Instead of interacting with Express directly, you will be interacting with Express Primer's classes.

The two primary classes are Endpoint and Router.

The gist is to create Endpoint classes (instead of Express route handlers), which can then be grouped and routed to URLs using a Router (instead of Express app[METHOD]).

Simple example

Here's a simple "hello world" Express app:

const express = require('express');
const app = express();

app.get('/', (req, res) => {
    
    res.send('hello world!');
});

app.listen(8080);

Here's the Express Primer version:

const { Endpoint, Router } = require('express-primer');

class HelloWorldEndpoint extends Endpoint {
    handler(req) {
        return 'hello world!';
    }
}

const router = new Router();
router.route('/', HelloWorldEndpoint);

const app = router.mount(); // an express app

app.listen(8080);

A bit different, but not by much, and the benefits of the Express Primer approach are not immediately obvious. For simple apps like this, Express is a better option. But, as your app becomes increasingly complex, things change:

Complex example

Consider the following API built in Express:

const express = require('express');
const app = express();

app.get('/api/v1/hello', (req, res) => {
    
    res.send('hello world!');
});

app.get('/api/v1/greeting', (req, res) => {
    
    res.send({ result: `${req.query.chosenGreeting} world!` });
});

app.listen(8080);

True to Express form, it's still compact and easily understandable. But as an API, this app presents no information about itself to the outside world or to other developers. There is no easy way to validate or constrain the chosenGreeting, or to even document to the client of this API what the inputs and outputs are of these endpoints.

Advantages in brevity are lost if validation and documentation are part of your goals (as they should be!). These problems become much more apparent as the number and complexity of endpoints grow.

Now, here's the Express Primer version:

const { Endpoint, Router } = require('express-primer');

/**
 * Create a "hello" endpoint.
 */
class HelloEndpoint extends Endpoint {
    
    operation() {
        return { summary: 'Generic greeting.' };
    }
    
    responseCodeSchemas() {
        // maps a response code to the expected JSON Schema for that code.
        return {
            '200': { 
               type: 'string',
               const: 'hello world!',
               contentMediaType: 'text/plain'
           }
        };
    }
    
    handler(req) {
        // returned items are passed to res.send
        // can also return Promises
        return 'hello world!';
    }
}

/**
 * Create a "greeting" endpoint.
 */
class GreetingEndpoint extends Endpoint {
    
    operation() {
        return { summary: 'Create your own greetings.' };
    }
    
    querySchema() {
        // a JSON Schema describing the expected req.query object.
        return {
            properties: {
                chosenGreeting: {
                    description: 'The greeting to use.',
                    type: 'string',
                    maxLength: 25,
                    default: 'hello'
                }
            }
        };
    }
    
    responseCodeSchemas() {
        // maps a response code to the expected JSON Schema for that code.
        return {
            '200': {
                type: 'object',
                contentMediaType: 'application/json',
                properties: {
                    result: { 
                        type: 'string',
                        description: 'The generated greeting.'
                    }
                },
                required: ['result']
            }
        };
    }
    
    handler(req) {
        // returned items are passed to res.send
        // can also return Promises
        return { result: `${req.query.chosenGreeting} world!` };
    }
}

/**
 * Route to the created endpoints.
 */
const router = new Router();

router.group('/api', router => {

   router.group('/v1', router => {
       
       router.route('/hello', HelloEndpoint);
       
       router.route('/greeting', GreetingEndpoint);
   });
   
   router.serveSpec('/spec');
});

const app = router.mount(); // an express app

app.listen(8080);

The above is clearly much more verbose than the Express method. But what have we gained from this extra work?

  • You are now clearly able to see the full description and constraints of all request parameters and response bodies, along with the corresponding response status code. :white_check_mark:
  • Request parameters are automatically validated before the handler is executed. :white_check_mark:
  • Invalid requests are automatically rejected with the appropriate 400 error. :white_check_mark:
  • Routes are very easily grouped. :white_check_mark:
  • An OpenAPI spec is generated and served at the /api/spec URL. :white_check_mark:
  • Documentation is automatically built from the served OpenAPI spec (via Swagger UI). :white_check_mark:

Other benefits that will be illustrated in later examples are:

  • Validate any request property.
  • Optionally validate responses.
  • The ability to return promises from request handlers.
  • Restrict middleware to specific groups.
  • Protect and document authenticated route groups.

Next steps

  • Look at the API and examples for the Endpoint class (coming soon).
  • Look at the API and examples for the Router class (coming soon).