# Apolitical Server

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

## Requirements

Requires the following to run:

- [node.js][node] 16.13.0+
- [yarn][yarn]

[node]: https://nodejs.org/en/download/
[yarn]: https://classic.yarnpkg.com/en/docs/install

## Installation

Install with `yarn`:

```sh
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__:

```js
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.

### Quickstart

The recommended way to use the __Apolitical Server__ is to `start` your own server with the appropriate parameters:

```js
// 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](https://expressjs.com/en/5x/api.html#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](http://localhost:3000/liveness) and [http://localhost:3000/readiness](http://localhost:3000/readiness).

The server can be stopped any time after its start like this:
```js
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](https://expressjs.com/en/5x/api.html#app). 

```js
// 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](tests/../test/integration/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. 

```js
// 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](https://github.com/mikenicholson/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](https://github.com/jaredhanson/passport#authenticate-requests) 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](tests/../test/integration/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. 

```js
// 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](https://expressjs.com/en/guide/error-handling.html).

```js
// 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:

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

For usage examples, see [errors.spec.js](tests/../test/integration/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](https://create-react-app.dev/docs/deployment#other-solutions).

```js
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](https://create-react-app.dev/docs/title-and-meta-tags/#generating-dynamic-meta-tags-on-the-server).

```js
// 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](tests/../test/integration/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`.

```js
// 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](tests/../test/integration/errors.spec.js) test cases.

### Swagger documentation

The __Apolitical Server__ also supports Swagger documentation through JSON files. For more info see [swagger-ui-express](https://www.npmjs.com/package/swagger-ui-express).

```js
// 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](http://localhost:3000/docs).

For usage examples, see [jwt.apolitical.spec.js](tests/../test/integration/jwt.apolitical.spec.js) test cases.

### Request Helper

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.

```js
// 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:

```js
const { buildQueryString } = request({});

// Build query string from request query object
const queryString = buildQueryString(req.query);
```
