hmpo-app
v5.0.7
Published
Bootstrap middleware and templating
Keywords
Readme
hmpo-app forms framework bootstrap
NOTE:
hmpo-appv5.0.2 and above uses GOV.UK Frontend v5+, which requires ECMAScript Modules (ESM) and a modern JavaScript bundler.How to Bundle ESM JavaScript
You must use a JavaScript bundler (such as Rollup, Webpack, or Vite) that supports ESM. Directly including these modules in
<script>tags in browsers is not supported.If your project or its dependencies use a mix of CommonJS (CJS) and ESM modules, you will need to configure Rollup with additional plugins to ensure everything is bundled correctly.
1. Install dependencies
npm install --save-dev rollup @rollup/plugin-commonjs @rollup/plugin-node-resolve @rollup/plugin-terser2. Example entry file (
assets/javascripts/app.js)import { initAll } from 'govuk-frontend'; import 'hmpo-components/all.js'; // ...your custom JS here initAll();3. Example Rollup config (
rollup.config.mjs)import commonjs from '@rollup/plugin-commonjs'; import { nodeResolve } from '@rollup/plugin-node-resolve'; import terser from '@rollup/plugin-terser'; export default { input: 'assets/javascripts/app.js', output: { file: 'public/javascripts/application.js', format: 'esm', sourcemap: true, }, plugins: [ nodeResolve(), commonjs(), terser() ], };4. Add a build script to your
package.json"scripts": { "build:js": "mkdir -p public/javascripts && rollup -c" }Build your bundle with:
npm run build:js5. Include the bundle in your HTML
<script type="module" src="/public/javascripts/application.js"></script>If you previously included scripts directly with
<script>, you will need to update your build pipeline to use ESM and a bundler.For more options and troubleshooting, see the Rollup documentation and plugin docs.
Usage
Simple usage
const { setup } = require('hmpo-app');
const { router } = setup();
router.use('/', require('./routes/example'));
Extended usage
const {
setup,
featureFlag,
businessFlag,
config,
logger,
redisClient,
linkedFiles
} = require('hmpo-app');
const {
app,
staticRouter,
router,
errorRouter
} = setup({
option: 'value'
});See example app for more details
Usage Details
setup(options?)
- Returned from
require('hmpo-app'). setup(options)Bootstraps the app. Run this as early on as possible to init the logger before it is used.
Parameters
options(Object) - Configuration options for the application:globalMiddlewareSetupFn(Function, optional) - Configures the global middleware setup process. Defaults toundefined.middlewareSetupFn(Function, optional) - Configures the middleware setup process. Defaults toundefined.
Returns
app- the top-level express app.staticRouter- an express router before session is initialised.router- an express router after session is initialised.errorRouter- an express router before the generic error handling used to handle custom errors.
More info on the options Object
Any of these options (except for config) can also be specified in a config file. The options passed to setup() override the options loaded from config files.
config - if false no config will be loaded.
APP_ROOT- Override app root directory detectionfiles='config/default(.json|.yaml|.yml)'- Array of config files to try to load. Missing files will fail silently.envVarName='HMPO_CONFIG'- Environment variable to parse to override config values.commandLineSwitch='-c'- Command line switch to load additional config files.merge=true- Merge new config with config from previous calls to setup.
env = NODE_ENV - Environment variable or 'development' environment.
port = 3000 - Port to bind to. If false the app will not listen to a port.
host = '0.0.0.0' - Host to bind to.
logs - See hmpo-logger for options passed to logger. See hmpo-logger for defaults. If false no logger is initialised.
requestLogging = true - Enable request logging (excluding public static files).
redis - If false redis is not initialised.
connectionString- Connection url used for connecting to a redis instance.host- Host name for connecting to a redis instance.port=6379- Port for connection to a redis instance....otherOptions- Any other options are passed toredis.- If neither
connectionStringorhostandportare specified, an in-memory redis is used.
errors - If false no error handler is set
startUrl='/'- Url to redirect to if a deep page is accessed as a new browser. Can be afunction(req, res).pageNotFoundView='errors/page-not-found'- View to render for page not found.sessionEndedView='errors/session-ended'- View to render if session is not found/expired.defaultErrorView='errors/error'- View to render for other errors.
urls
public='/public'- Base URL for public static assets.publicImages='/public/images'- Base URL for public sttic images.version='/version'- Base URL for version endpoint, orfalseto disable.healthcheck='/healthcheck'- Base URL for healthcheck endpoint, orfalseto disable.
publicDirs = ['public'] - Array of paths to mount on the public route, relative to APP_ROOT.
publicImagesDirs = ['assets/images'] - Array of paths to mount on the public images route, relative to APP_ROOT.
publicOptions = {maxAge: 86400000} - Options passed to the express static middleware.
views = 'views' - Array of view directories relative to APP_ROOT.
nunjucks - Options passed to nunjucks templating contructor, or false to disable
dev=env==='development'- Runnunjucksin developer mode for more verbose errors.noCache=env==='development'- Don't cache compiled template files.watch=env==='development'- Watch for changes to template files....otherOptions- Any other options are passed tonunjucks.configure
locales = '.' - Array of locales base directories (containing a 'locales' directory) relative to APP_ROOT.
translation - Options passed to hmpo-i18n translation library, or false to disable.
noCache=env==='development'- Don't cache templated localisation strings.watch=env==='development'- Watch for changes to localisation files.allowedLangs=['en','cy']- Array of allowed languages.fallbackLang=['en']- Array of languages to use if translation not found is current language.cookie={name: 'lang'}- Cookie settings to use to store current language.query='lang'- Query parameter to use to change language, orfalseto disable....otherOptions- Any other options are passed to hmpo-i18n.
modelOptions - Configuration for model options helper to be used with hmpo-model.
sessionIDHeader='X-SESSION-ID'- Session ID request header to pass through to models.scenarioIDHeader='X-SCENARIO-ID'- Stub scenario ID request header to pass through to models.
helmet - Configuration for Helmet, or false to only use frameguard and disable x-powered-by.
disableCompression = false - disable compression middleware.
cookies - Configuration for cookie parsing middleware.
featureFlag
Middleware returned from require('hmpo-app').
getFlags(req) - Return all session and config feature flags.
isEnabled(flag, req) - Check if a feature flag is enabled in session or config.
isDisabled(flag, req) - Check if a feature flag is disabled in session or config.
redirectIfEnabled(flag, url) - Middleware to redirect if a flag is enabled.
redirectIfDisabled(flag, url) - Middleware to redirect if a flag is disabled.
routeIf(flag, handlerIf, handlerElse) - Middleware to run different handler depending on status of a feature flag.
Example Usage
const { featureFlag } = require('hmpo-app');
const enabledMiddleware = (req, res, next) => res.send('flag enabled');
const disabledMiddleware = (req, res, next) => res.send('flag disabled');
router.use(featureFlag.routeIf('flagname', enabledMiddleware, disabledMiddleware));businessFlag
Middleware returned from require('hmpo-app').
getFlags(req) - Return all session and config business flags.
isEnabled(flag, req) - Check if a business flag is enabled in session or config.
isDisabled(flag, req) - Check if a business flag is disabled in session or config.
redirectIfEnabled(flag, url) - Middleware to redirect if a flag is enabled.
redirectIfDisabled(flag, url) - Middleware to redirect if a flag is disabled.
routeIf(flag, handlerIf, handlerElse) - Middleware to run different handler depending on status of a feature flag.
Example Usage
const { business } = require('hmpo-app');
const enabledMiddleware = (req, res, next) => res.send('flag enabled');
const disabledMiddleware = (req, res, next) => res.send('flag disabled');
router.use(business.routeIf('flagname', enabledMiddleware, disabledMiddleware));Feature Flag & Business Flag Semantic Differences:
Feature Flags are typically used for technical or deployment toggles, like enabling a new logging format or switching between frontend flows. Feature Flags are meant to be temporary.
Business Flags are used for controling policy-driven or operational logic, service variation, or business rule change. Business Flags are meant to be semi-permenant, until the business says otherwise.
config
A config helper returned from require('hmpo-app').
Returns
defaultFiles - An array of default config files: [
'config/default.json',
'config/default.yaml',
'config/default.yml'
];
setup(configOptions?) - A function for config setup that takes an options Object containing possible params:
{ APP_ROOT, seed, files = defaultFiles, envVarName = 'HMPO_CONFIG', commandLineSwitch = '-c', merge = true, _commandLineArgs = process.argv, _environmentVariables = process.env }- Defaults to empty Object if no options provided.
get(path, defaultIfUndefined) / config(path, defaultIfUndefined) - Get a value from loaded config by dot separated path, or a default if not found or undefined. If any part of the path is not found, the default will be returned.
Example Usage
const { config } = require('hmpo-app');
let defaultFiles = config.defaultFiles;
// Setup config
if (options.config !== false) config.setup(options.config);
const value = config.get('config.path.string', 'default value');Due to the way config.js uses Object.assign(), you can use config(path, defaultIfUndefined) and config.get(path, defaultIfUndefined) interchangeably - depending on which you find better for readability.
E.g.
const value = config('config.path.string', 'default value');
// OR
const value = config.get('config.path.string', 'default value');logger
Returns
setup(options?) - Setup the logger by passing an options Object. If no options are supplied it will default to checking config.get('logs', {}). This passes the provided options directly to hmpoLogger.config().
get(name?, level?) / logger(name?, level?) - Get a new logger with an optional name (defaults to :hmpo-app) and an optional level (defaults to 1).
Example Usage
const { logger } = require('hmpo-app');
logger.setup(); // Setup from config.
// or
logger.setup({ foo: 'bar' }); // Setup from options
const log = logger(':name');
log.info('log message', { req, err, other: 'metedata' });
// or
logger().info('log message', { req, err, other: 'metedata' });Due to the way logger.js uses Object.assign(), you can use logger(name) and logger.get(name) interchangeably - depending on which you find better for readability.
E.g.
const myLogger1 = logger('logger1');
// OR
const myLogger2 = logger.get('logger2');redisClient
Returns
redisClient() / getClient() - Returns a redis client.
client - The redisClient, defaults to null.
setup(options?) - Returns a modified redisClient.client.
- Closes an existing client using provided
close()and creates a new one using the provided options. - All options are passed to
redis.createClient(). See Node Redis Docs for more info. - If no
optionsspecified, this will default to checkingconfig.get('redis', {}). optionsassumes the following parameters:{ connectionString, host, port = 6379, ...redisOptions }
close(callback) - Quits a redisClient if it exists / is connected, then sets redisClient.client = null. Finally, fires the provided callback function.
Due to the way redis-client.js uses Object.assign(), you can use redisClient() and redisClient.getClient() interchangeably - depending on which you find better for readability.
Example Usage
const { redisClient } = require('hmpo-app');
// Setup the Redis client
redisClient.setup({
connectionString: 'redis://localhost:6379',
// OR
host: 'localhost',
port: 6379,
// Optional: any other redis.createClient options
socket: {
reconnectStrategy: retries => Math.min(retries * 50, 2000)
}
});
// Get the Redis client instance
const client = redisClient(); // same as redisClient.getClient()
client.set('foo', 'bar');
client.get('foo').then(console.log); // Outputs: bar
// Gracefully close the client when shutting down
redisClient.close(() => {
console.log('Redis connection closed');
});Further Examples
- The example app.
- The hmpo-logger library.
- The hmpo-components library.
