express-server-app

0.5.3 • Public • Published

express-server-app

A minimal opinionated set of tools to create Web and REST servers. On top of 🚀Express, it provides: 🛠 CLI, 📖 logging, ✅ JSON Schema validation, ️⚠️ HTTP errors management, 🎚 project configuration, ⛑ HTTP security ...

npm Code Climate coverage CircleCI Code Climate maintainability MIT

Philosophy

It does not aim to cover every needs of a Web or REST API server. It only aims to provide a stable base of development.

We do not want to recreate everything ! But to provide a set of existing libraries which are already used by many.

It mainly uses the following modules:

Features

🎚 Config

Reads server configuration from .env and config/config-<NODE_ENV>.js files.

📖 Logging

Creates a default logger using pinojs and add it to express server.

⛑ Basic security

The server adds by default the cors and helmet middlewares, and forces HTTPS protocol in production environment.

✅ JSON Schema request validation

Requests params can be validated using Ajv and JSON Schema.

🛠 Async middlewares helpers

Allows to write middlewares using async/await and promises.

⚠️ Error formatting for REST APIs

Errors are formatted using Boom and sent back as JSON with correct errors codes.

🛠 CLI tools to run and debug the app

The following commands are available:

  • debug: run your server with node inspect to allow debugging
  • dist: run your server for production (NODE_ENV=production is not added automatically)
  • start: run your server for development without debugger
  • test: test your server

Getting started

Installation

Using npm

npm install express-server-app

Using yarn

yarn add express-server-app

Usage

See the example in the repo.

The entry point of your server must be the file server.js

Create a simple server

// server.js
const { application } = require('express-server-app');
 
// Initializes express application and add minimum middlewares (security, request parsing, logging)
// Application should be unique.
// If you need to create sub apps or routers, use express as usual !
// See .application() documentation in the following for an example.
const app = application().useInitialMiddlewares();
 
// Add your own routes using express api
app.get('/coucou', (req, res) => { res.send('hibou'); });
 
// Start the server on the default port (3000)
app.start();

Create a REST API

const { application } = require('express-server-app');
 
const app = application().useInitialMiddlewares();
 
// .. Write your own routes and middlewares ...
 
app.useHealthyRoute() // Add the route /healthy for health control of the server
    .useApiFinalMiddlewares() // Add middlewares for REST API
    .start();

Logging

// Require the default logger
const { log } = require('express-server-app');
 
// Log on info level (see pino api for details)
// Note that log() is a function!
log().info('Hello!');

Config

# .env 
PORT=9000
// config/config-development.js
module.exports = {
    serverPort: process.env.PORT,
};
// server.js
const { application, config } = require('express-server-app');
// ...
app.start(config.serverPort);

Request validation

const { application, validator } = require('express-server-app');
 
const app = application().useInitialMiddlewares();
 
app.get(
    '/valid',
    validator().validate({ // Note that validator() is a function!
        query: { // JSON Schema object
            type: 'object',
            additionalProperties: false,
            properties: {
                param: { type: 'string' },
            },
            required: ['param'],
        },
    }),
    (req, res) => res.send('valid'),
);
 
app.useApiFinalMiddlewares()
    .start();

REST API errors

const { application } = require('express-server-app');
const Boom = require('@hapi/boom'); // Require Boom !
 
const app = application().useInitialMiddlewares();
 
app.get(
    '/notImplemented',
    (req, res, next) => next(Boom.notImplemented()),
);
 
app.useApiFinalMiddlewares()
    .start();

Express async handlers

const { application, wrapAsync } = require('express-server-app');
 
const Person = require('./models/Person');
 
const app = application().useInitialMiddlewares();
 
app.get(
    '/persons',
    wrapAsync(async (req, res, next) => { await Person.findAll(); }),
);
 
app.useApiFinalMiddlewares()
    .start();

Launch the server

In development:

express-server-app start

In production:

NODE_ENV=production express-server-app dist

CLI

debug

Run the server using nodemon with the --inspect-brk flag to allow livereload and debuggers. It also pipes output to pino-pretty to render JSON logs more friendly.

Example (using npx):

npx express-server-app debug

You may also add scripts to your package.json:
Example:

...
"scripts"{
    "debug": "express-server-app debug",
    "dist": "express-server-app dist",
    "start": "express-server-app start",
    "test": "express-server-app test",
}
...

dist

Run the server using node only. It does not set the env variables NODE_ENV to production so you may have to!

Example: (using package.json scripts)

NODE_ENV=production npm run dist

start

Run the server using nodemon and pino-pretty.

Example: (using package.json scripts)

npm start

test

Test the server using jest.

Example: (using package.json scripts)

npm test

API

express-server-app

An object containing the following tools:

{
    application, // create server
    config, // get config
    log, // global logger
    middlewares, // defaults middlewares
    validator, // JSON Schema request validator
    wrapAsync, // helper for request async handlers
} = require('express-server-app');

.application

.application()

It returns ApplicationInstance, an express application augmented with the following methods:

  • start
  • trustProxy
  • useApiFinalMiddlewares
  • useHealthyRoute
  • useInitialMiddlewares
  • useRootRoute

Example:

const { application } = require('express-server-app');
const app = application();

Usage of routers
ApplicationInstance should be unique.
If you want sub express applications or routers use express api directly (as you would do without express-server-app).

Example:

// route.js
const express = require('express');
 
const router = express.Router(); // An express Router
// ... add your routes using router.get() etc...
 
module.exports = router;
// app.js
const { application } = require('express-server-app');
 
const router = require('./router');
 
const app = application().useInitialMiddlewares();
 
// Use your router as usual
app.use('/route', router);
 
app.start();

.application.start(app, [port])

Start the application passed as parameter optionnaly on port [port]. Default port is 3000

.application.trustProxy(app, [value])

Call express.set('trust proxy') to allow proxy "forward" headers.
See express documentation for details.
Default trusted proxy ip is 127.0.0.1

ApplicationInstance.start([port])

Start the server optionnaly on port passed as parameter.

Example:

const { application } = require('express-server-app');
const app = application();
app.start(80);

ApplicationInstance.trustProxy(value)

Trusts proxy passed as parameter. See express documentation for details.

Example:

const { application } = require('express-server-app');
const app = application();
app.trustProxy('loopback, 123.123.123.123');

ApplicationInstance.useApiFinalMiddlewares([options])

Add middlewares for REST API application.

Those "final" middlewares must be at the end of the chain. Call this method just before start().
See .middlewares.getApiFinalMiddlewares([options]) for details.

Example:

const { application } = require('express-server-app');
const app = application();
// ... add your routes
app.useApiFinalMiddlewares().start();

ApplicationInstance.useHealthyRoute()

Add the route /healthy.
See .middlewares.healthy for details.

Example:

const { application } = require('express-server-app');
const app = application();
// ... add your routes
app.useHealthyRoute()
    .useApiFinalMiddlewares() // last middlewares !
    .start();

ApplicationInstance.useInitialMiddlewares([options])

Add default middlewares any server.

Those middlewares must be at the beginning of the chain. Call this method just after create the application.
See .middlewares.useInitialMiddlewares([options]) for details.

Example:

const { application } = require('express-server-app');
const app = application()
    .useInitialMiddlewares(); // first middlewares !
// ... add your routes
app.useApiFinalMiddlewares() // last middlewares !
    .start();

ApplicationInstance.useRootRoute()

Set a default index route /.
See .middlewares.root for details.

Example:

const { application } = require('express-server-app');
const app = application();
// ... add your routes
app.useRootRoute()
    .start();

.config

An object containing the config object defined in file config/config-<NODE_ENV>.js.

When you require express-server-app, it reads the environment variables defined in .env files using dotenv.
Then you can use this variables using the process.env object.

The JavaScript config files directory can be changed using the environment variable CONFIG_DIR.

The .env config management has been copied from create-react-app. See its documentation for details.

Example:

# .env 
CONFIG_DIR=conf
LOG_LEVEL=30
SECRET_ACCESS_KEY=abcdef1234
// conf/config-production.js
module.exports = {
    tiersApi: {
        accessKey: process.env.accessKey,
    },
};
// server.js
const { config, log() } = require('express-server-app');
log().level = process.env.LOG_LEVEL;
tiers.setAccessKey(config.accessKey);

If needed you can override the .env file path using the environment variable DOTENV_PATH.
Example:

DOTENV_PATH=.secondary.env npm start

This will load the environment variables defined in the files .secondary.env.development and .secondary.env.development.local.

.log

.log()

Returns the global logger. By default it returns a Pino instance.

Example:

const { log } = require('express-server-app');
const logger = log();
logger.info('Hello!');

Logging of sensitive informations
Default logger removes Authorization header from logs. Its value is replaced by "***"

.log.setLogger(lg)

Override the global logger with the logger passed as parameter.

Returns the new logger.

Example:

const { log } = require('express-server-app');
const logger = log.setLogger(pino());

.middlewares

.middlewares.enableCors()

A middleware to enable the CORS.
By default, the environment variable CORS_ORIGIN_WHITELIST is used to set the allowed origins.
If CORS_ORIGIN_WHITELIST is not defined all origins are allowed.

# .env 
# one or multiple domains comma separated 
CORS_ORIGIN_WHITELIST=https://www.example1.com
CORS_ORIGIN_WHITELIST=https://foo.com,https://bar.com
CORS_ORIGIN_WHITELIST=https://www.example1.com,/\\.example2\\.com$/,https://www.53js.fr
# RegExp : you have to double the slash (\ => \\) 
CORS_ORIGIN_WHITELIST=/localhost/
CORS_ORIGIN_WHITELIST=/\\.example2\\.com$/
# Boolean 
CORS_ORIGIN_WHITELIST=true
# special values 
CORS_ORIGIN_WHITELIST=*

For more options you can use directly your own cors middleware:

const { cors } = require('cors');
const { middlewares } = require('express-server-app');
const initialMiddlewares = getInitialMiddlewares({
    cors: cors({ methods: ['POST'] }), // override default cors middleware
});
application()
    .useInitialMiddlewares(initialMiddlewares)
    //...

.middlewares.forceHttps([port])

A middleware to force https permanent redirection in production environment only.
Pass parameter port to change default 443 port for the redirection.

.middlewares.handle404

A REST middleware that throws Boom.notFound() if route is not found.

.middlewares.handleErrors

A REST middleware that respond with errors as JSON with correct status codes and headers using Boom.

.middlewares.handleValidationErrors

A REST middleware that handles JSON Schema validation errors and throw a Boom error.

.middlewares.healthy

A route that sends a minimal json (boolean true) used to monitor the server health.

.middlewares.root

A default index route that sends a short text.

.middlewares.getInitialMiddlewares(options)

Returns an array of middlewares that should be added at the beginning of the express middleware chain.

Middlewares (order is important):

  • helmet
  • forceHttps
  • cors
  • logger
  • express.json
  • express.urlencoded

It is possible to override default middlewares using the options parameter :

options

An object containing middlewares :

{
    cors, // default: .middlewares.enableCors()
    forceHttps, // default: .middlewares.forceHttps()
    helmet, // default: helmet()
    json, // default: express.json()
    logger, // default: pinoMiddleware({ logger: log() })
    urlencoded, // default: express.urlencoded({ extended: true })
}

Set a middleware to false to disable it.

Example:

const { middlewares } = require('express-server-app');
const initialMiddlewares = getInitialMiddlewares({
    cors: cors({ origin: 'http://example.com' }), // override cors middleware
    helmet: false, // disable helmet
});

.middlewares.getApiFinalMiddlewares(options)

Returns an array of middlewares that should be added at the end of the express middleware chain.

Middlewares (order is important):

  • notFound
  • validationErrors
  • errors

options

An object containing middlewares :

{
    errors, // default: .middlewares.handleErrors
    notFound, // default: .middlewares.handle404
    validationErrors, // default: .middlewares.handleValidationErrors
}

Set a middleware to false to disable it.

Example:

const { middlewares } = require('express-server-app');
const finalMiddlewares = getFinalMiddlewares({
    notFound: (req, res, next) => next(Boom.notImplemented()),
    validationErrors: false, // disable validation error middleware
})

.validator

.validator()

Returns the global JSON Schema validator.

See express-json-validator-middleware, ajv, JSON Schema for details.

Example:

const { validator } = require('express-server-app');
app.get(
    '/valid',
    validator().validate({
        query: {
            type: 'object',
            additionalProperties: false,
            properties: {
                param: { type: 'string' },
            },
            required: ['param'],
        },
    }),
    (req, res) => res.send('valid'),
);

.validator.setValidator(vtor)

Override the global validator with the validator passed as parameter.

Returns the new validator.

Example:

const { validator } = require('express-server-app');
validator.setLogger(new Validator());

.wrapAsync

.wrapAsync(fn)

Wraps the fn request handler to allow async/await and Promise handlers.

Returns the wrapped middleware.

Example:

const { wrapAsync } = require('express-server-app');
// ...
app.get(
    '/persons',
    wrapAsync(async (req, res, next) => { await Person.findAll(); }),
);

Maintainers

Package Sidebar

Install

npm i express-server-app

Weekly Downloads

12

Version

0.5.3

License

MIT

Unpacked Size

30.5 kB

Total Files

18

Last publish

Collaborators

  • 53js