@apolitical/server

Node.js module to encapsulate Apolitical's express server setup

Usage no npm install needed!

<script type="module">
  import apoliticalServer from 'https://cdn.skypack.dev/@apolitical/server';
</script>

README

Apolitical Server

Node.js module to encapsulate Apolitical's express server setup

Requirements

Requires the following to run:

Installation

Install with yarn:

yarn add @apolitical/server

Usage

This section covers how to use most of the functionally exposed by the Apolitical Server module.

First of all, include the Apolitical Server:

const { start, stop, jwt, errors, middlewares } = require('@apolitical/server');

Bear in mind, the start, stop, jwt, errors and middlewares variables will be used and explained in the examples listed below.

Quickstart

The recommended way to use the Apolitical Server is to start your own server with the appropriate parameters:

// Start the server
start({ port: 3000 });

The example shows how to start the server by providing the port parameters. The port is required to define where the server will be listening for incoming requests. For more info see express app.listen.

The Apolitical Server comes with liveness and readiness probes, so you don't need to implement them. These probes are available at http://localhost:3000/liveness and http://localhost:3000/readiness.

The server can be stopped any time after its start like this:

stop();

NOTE! The start and stop functions are asynchronous, so you would have to await for them.

App loader

The Apolitical Server completely delegates how the application is loaded by exposing the appLoader function. The appLoader function will be called with the express app as a parameter. You are in control of defining endpoints, middlewares, and so on. For more info see express app.

// Example controller
function exampleController(req, res) {
  res.status(200).json({ ok: true });
}

// Use the app loader to register your endpoints
function appLoader(app) {
  app.get('/example', exampleController);
}

// Start the server
start({ appLoader, port: 3000 });

For usage examples, see example.spec.js test cases.

JWT authentication

The Apolitical Server has integrated JSON Web Token (JWT) functionality. The JWT authentication layer is intended to be used to secure endpoints from requests without a valid session token.

// The JWT strategy session secret
const SESSION_SECRET = 'hello';

function appLoader(app) {
  // Setup the Apolitical JWT strategy 
  jwt.apolitical.setup(SESSION_SECRET);
  // Use the authentication middleware to reject unauthorised requests
  app.use(middlewares.authentication());
  // The example endpoint is now protected
  app.get('/example', exampleController);
}

// Start the server
start({ appLoader, port: 3000 });

The JWT authentication must be defined with two steps:

  • First, the jwt.apolitical.setup function initialises the JWT strategy with the use of the passport-jwt module. In order to setup the JWT strategy, the SESSION_SECRET (string) variable must be provided.
  • Then, the middlewares.auth function executes the JWT strategy with the use of the passport module. If the request is authorised, the user variable will be assigned to the req object. The user variable contains the information stored in the token. In case the request is unauthorised, an error will be thrown.

NOTE! The Apolitical JWT must be included in the request as a Cookie header and cookie must follow the format apolitical_auth={token}. The token should be encoded with the same SESSION_SECRET.

For usage examples, see jwt.apolitical.spec.js test cases.

JWT authorisation

The Apolitical Server has integrated JSON Web Token (JWT) functionality. The JWT authorisation layer is intended to be used to protect endpoints from request with session token without the right permissions.

// The JWT strategy session secret
const SESSION_SECRET = 'hello';

function appLoader(app) {
  // Setup the Apolitical JWT strategy 
  jwt.apolitical.setup(SESSION_SECRET);
  // Use the authentication middleware to reject unauthorised requests
  app.use(middlewares.authentication());
  // The example endpoint can only be accessed by admin users
  app.get('/example', middlewares.authorisation(), exampleController);
}

// Start the server
start({ appLoader, port: 3000 });

As before, the Apolitical token will be validated by the authentication middleware, and then, the authorisation middleware will check that the role property on the session token to determine the permissions.

Error handling

The Apolitical Server comes with a default error handler so you don’t need to write your own to get started. It’s important to ensure that your server catches all errors that occur while running your business logic. For more info see express error handling.

// Error controller
function errorController(req, res, next) {
  next(new errors.BadRequest('Some Error Message', ['some-error']));
}

function appLoader(app) {
  app.get('/error', errorController);
}

// Start the server
start({ appLoader, port: 3000, handleErrors: true });

The errors object includes a predefined set of errors: BadRequest, Forbidden, NotFound, TooManyRequests and InternalError. Each error is an extension of the generic JavaScript Error object. These errors are designed to store the response status code, error message, and error key words (an array of strings).

The start server function can take the handleErrors parameter. When enabling the handleErrors parameter, you can forward an error with the use of the next function on your controller and the server will take care of producing the error response. In the example above, all of the incoming requests to the /error endpoint are going to produce a 400 (bad request) error with the following JSON object on the body:

{
  "message": "Some Error Message",
  "errors": ["some-error"]
}

For usage examples, see errors.spec.js test cases.

Static server

The Apolitical Server can be used for serving static files. This functionality is especially useful to host React apps in production. For more info see create react app deployment.

const path = require('path');

// Start the server
start({
  port: 3000,
  staticFiles: {
    baseUrl: '/frontend/',
    folderPath: path.join(__dirname, 'build'),
    indexFilePath: path.join(__dirname, 'build', 'index.html')
  },
});

The staticFiles object must include:

  • The baseUrl to determine the URL path where the static files are exposed from.
  • The folderPath to determine the directory where the static files are located.

The indexFilePath is optional, and determines where the index.html file is located.

SEO controller

In case indexFilePath variable is not present, the appLoader can be used to define the routing for the index.html file. That's very useful when defining SEO controllers. For more info see create react app dymanic meta tags.

// Read index.html file
const indexFile = fs.readFileSync(path.join(__dirname, 'index.html'), 'utf8');

// SEO controller
function seoController(req, res) {
  const indexModified = indexFile.replace('__OG_TITLE__', 'some-title');
  res.send(indexModified);
}

function appLoader(app) {
  app.get('/frontend/*', seoController);
}

// Start the server
start({
  appLoader,
  port: 3000,
  staticFiles: {
    baseUrl: '/frontend/',
    folderPath: path.join(__dirname, 'build'),
  },
});

For usage examples, see static.spec.js test cases.

Error redirect

If there's an error when serving static files such as the index.html, it's not consumed by machines (frontend code) like it would when implementing an API endpoint. Instead, the errors are consumed by the user. For that reason, the Apolitical Server provides you with a complementary parameter to the handleErrors that allows you to redirect the users to another URL when something goes wrong. This parameter is called redirectURL.

// Start the server
start({
  appLoader,
  handleErrors: true,
  port: 3000,
  redirectURL: 'http://localhost:3000/not-found'
  staticFiles: {
    baseUrl: '/frontend/',
    folderPath: path.join(__dirname, 'build'),
  },
});

NOTE! The redirectURL parameter must be defined as an absolute URL.

For usage examples, see errors.spec.js test cases.

Swagger documentation

The Apolitical Server also supports Swagger documentation through JSON files. For more info see swagger-ui-express.

// Specify the Swagger document
const swaggerDocument = require('./doc/swagger.json');

// Start the server
start({ appLoader, port: 3000, swaggerDocument });

The swaggerDocument variable can be provided when starting the server to define the API documentation. The documentation can be found at http://localhost:3000/docs.

For usage examples, see jwt.apolitical.spec.js test cases.