express-server-app
v0.5.3
Published
A minimal opinionated set of tools to create Web and REST servers
Downloads
76
Maintainers
Readme
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 ...
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 debuggingdist
: run your server for production (NODE_ENV=production is not added automatically)start
: run your server for development without debuggertest
: 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(); }),
);