Node.js module to encapsulate Apolitical's express server setup
Requires the following to run:
Install with yarn
:
yarn add @apolitical/server
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, request } = require('@apolitical/server');
Bear in mind, the start
, stop
, jwt
, errors
, middlewares
, request
variables will be used and explained in the examples listed below.
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.
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.
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 tosetup
the JWT strategy, theSESSION_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, theuser
variable will be assigned to thereq
object. Theuser
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.
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.
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.
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.
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.
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.
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.
The Apolitical Server comes with a request helper functions.
One of those functions is buildOptions
that can be used to generate an apolitical_auth
header cookie when needed. This functionality is especially useful to generate an admin token when making request from one api to another.
// Instantiate the hellper with a session secret
const { buildOptions } = request({ sessionSecret: 'hello' });
// If generating an admin token: authToken must be truthy boolean
buildOptions({ headers: { authToken: true }})
// If using token passed in: authToken must be a string
buildOptions({ headers: { authToken: 'some-token' }})
Also, you can use buildQueryString
to stringify a query variable from a valid Express req.query
object:
const { buildQueryString } = request({});
// Build query string from request query object
const queryString = buildQueryString(req.query);