Core delivery platform Node.js Backend Template.
- Related documentation
- Requirements
- Local development
- OpenAPI Specification
- Development helpers
- Docker
- Postman Collection
- Example Folder Structure
- Licence
This service works alongside the grants-ui frontend. That README captures the end-to-end user journey and shared engineering practices. Useful companion sections include:
- DXT Forms Engine Plugin – how journeys are composed and which payloads this backend receives
- Session Rehydration – lifecycle of state stored in MongoDB by this service
- Structured Logging System – log code conventions shared across UI and backend services
- Analytics – how application telemetry is captured on the frontend, complementing the events emitted here
Use this README for backend-specific setup; refer to the frontend README when you need context about journeys, shared tooling, or logging standards.
Please install Node.js version 24 or higher and npm =v11.x.x (the project is routinely tested with npm v11). The exact minimum version requirement is specified in package.json (engines.node). You will find it
easier to use the Node Version Manager nvm
To use the correct version of Node.js for this application, via nvm:
cd grants-ui-backend
nvm useFor a self-contained local environment (service plus MongoDB), use the provided Compose file:
docker compose up --buildThis builds the development image and starts the dependencies defined in compose.yml. The backend is available on http://localhost:3001 by default. Stop the stack with:
docker compose downTo spin up the full stack with the frontend, follow the Docker guidance in grants-ui.
If you prefer to run the application directly on your machine, follow the steps below.
Install application dependencies:
npm installCreate your environment configuration file. You can use the provided example as a template:
cp env.example.sh .envRequired variables for local development:
MONGO_URI– address of your MongoDB instance (default:mongodb://127.0.0.1:27017)GRANTS_UI_BACKEND_AUTH_TOKEN– 64 character lowercase hexadecimal string (generate withopenssl rand -hex 32)GRANTS_UI_BACKEND_ENCRYPTION_KEY– 64 character lowercase hexadecimal string (generate withopenssl rand -hex 32)APPLICATION_LOCK_TOKEN_SECRET– 64 character lowercase hexadecimal string (generate withopenssl rand -hex 32)
Optional MongoDB configuration (sensible defaults are provided):
MONGO_DATABASE– database name (default:grants-ui-backend)MONGO_MAX_POOL_SIZE– maximum connection pool size (default:25)MONGO_MIN_POOL_SIZE– minimum connection pool size (default:5)MONGO_MAX_IDLE_TIME_MS– idle connection timeout in milliseconds (default:60000)
Application lock configuration (required for lock-protected routes):
APPLICATION_LOCK_TOKEN_SECRET– secret key for signing lock tokens (64 character hex string, generate withopenssl rand -hex 32)APPLICATION_LOCK_TTL_MS– lock timeout in milliseconds (default:14400000- 4 hours)
An extended reference with all available configuration options is available in env.example.sh.
Keep these values in sync with the frontend configuration described in the grants-ui environment guidance so clients can authenticate successfully.
To run the application in development mode run:
npm run devTo test the application run:
npm run testIntegration tests rely on Docker (via Testcontainers) and can be run with:
npm run test:integrationThe frontend documents complementary UI testing patterns in its testing framework section.
Husky installs a pre-commit hook during npm install. The hook runs npm run format:check, npm run lint, and npm test, mirroring the workflow in grants-ui. If commits are blocked:
- run
npm run setup:huskyto reinstall the hooks - address formatting or lint failures with
npm run lint:fix - fix failing tests locally before committing
To mimic the application running in production mode locally run:
npm startAll available Npm scripts can be seen in package.json. To view them in your command line run:
npm runCommon development scripts:
npm run dev– Run the application in development mode with auto-reloadnpm run dev:debug– Run in development mode with debugger breakpoint supportnpm start– Run the application in production modenpm test– Run unit tests with coveragenpm run test:watch– Run tests in watch modenpm run test:integration– Run integration tests (requires Docker)npm run lint– Check code for linting errorsnpm run lint:fix– Automatically fix linting errorsnpm run format– Auto-format code with Prettiernpm run format:check– Check code formatting without making changesnpm run generate:auth-header– Generate Bearer token for API authenticationnpm run generate:lock-header– Generate lock token for application lock-protected routesnpm run generate:lock-release-header– Generate lock release token for application lock release route
To update dependencies use npm-check-updates:
The following script is a good start. Check out all the options on the npm-check-updates
npx npm-check-updates --interactive --format groupIf you are having issues with formatting of line breaks on Windows update your global git config by running:
git config --global core.autocrlf falseAn OpenAPI 3.1 specification is available at the repository root:
- File: openapi.yaml
How to view it:
- Use any OpenAPI viewer or IDE plugin/extension (e.g. Swagger Viewer for VS Code or natively in IntelliJ).
Keeping the spec up-to-date:
- When you add or change routes (see src/plugins/router.js and src/routes/*), update openapi.yaml accordingly.
Application logs follow the shared, code-driven format used by the Grants UI frontend. Log codes live in src/common/helpers/logging/log-codes.js and are validated on startup; unit tests exist alongside the helpers (src/common/helpers/logging/*.test.js). When introducing new log codes, mirror the approach described in the frontend structured logging guide and update the relevant tests.
Mongo documents written to the grant-application-state collection are rehydrated by the frontend during user journeys. Review the frontend session rehydration documentation before modifying stored shapes or lifecycle expectations, and update the OpenAPI schema plus Postman collection accordingly.
The service’s MongoDB connection can be tuned via the following environment variables (see src/config.js). Sensible defaults are provided for local development, so you only need to override them when required by your environment or performance profile.
-
MONGO_URI(default:mongodb://127.0.0.1:27017) Connection string for your MongoDB deployment. -
MONGO_DATABASE(default:grants-ui-backend) Database name used by the service. -
MONGO_MAX_POOL_SIZE(default:25) Maximum number of connections in the client pool. -
MONGO_MIN_POOL_SIZE(default:5) Minimum number of connections to keep in the pool. -
MONGO_MAX_IDLE_TIME_MS(default:60000) How long an idle connection may remain in the pool before being closed, in milliseconds.
This service enforces exclusive application access to grant applications using a MongoDB-backed locking mechanism.
Locks are scoped to a single application, identified by:
grantCodegrantVersionsbi(Single Business Identifier)
Only one user from a given business may view/edit a given application at a time.
Lock-protected routes require a JWT token in the x-application-lock-owner header. This token identifies the user attempting to acquire the lock and includes the application scope (SBI, grantCode, grantVersion).
When a request enters a route protected by the application lock pre-handler, the backend:
- Extracts and validates the lock token from the
x-application-lock-ownerheader - Attempts to acquire or refresh a lock for the authenticated user
- Refreshes the lock if the same user already holds it
- Blocks access if another user holds an active lock
Locks are time-limited (TTL-based) and automatically expire if the user becomes inactive. The default TTL is 4 hours, configurable via APPLICATION_LOCK_TTL_MS.
If a lock expires, it can be taken over by another user.
Lock token format:
The lock token is a JWT containing:
sub– User identifier (DefraID user ID)sbi– Single Business IdentifiergrantCode– Grant application codegrantVersion– Grant scheme versiontyp– Token type (must be'lock')
Generate lock tokens using npm run generate:lock-header (see Generating an Application Lock Header).
Locking is enforced at the request level using a Hapi pre-handler:
options: {
pre: [{ method: enforceApplicationLock }]
}If another user holds the lock, the request is rejected with:
- HTTP 423 – Locked
- Message: Another applicant is currently editing this application
Lock state is stored in MongoDB in the grant-application-locks collection.
Each lock document contains:
- application identifiers (grantCode, grantVersion, sbi)
- ownerId (DefraID user identifier)
- lockedAt
- expiresAt
A unique index ensures only one active lock exists per application.
- Lock acquisition is atomic and safe under concurrent access.
- Lock contention is treated as an expected condition, not an error.
- Lock release on submission or sign-out is handled at route / workflow level and is out of scope for this section.
We are using forward-proxy which is set up by default. To make use of this: import { fetch } from 'undici' then
because of the setGlobalDispatcher(new ProxyAgent(proxyUrl)) calls will use the ProxyAgent Dispatcher
If you are not using Wreck, Axios or Undici or a similar http that uses Request. Then you may have to provide the
proxy dispatcher:
To add the dispatcher to your own client:
import { ProxyAgent } from 'undici'
return await fetch(url, {
dispatcher: new ProxyAgent({
uri: proxyUrl,
keepAliveTimeout: 10,
keepAliveMaxTimeout: 10
})
})The frontend relies on the same proxy configuration; see its proxy documentation if you are troubleshooting cross-repo HTTP behaviour.
Build:
docker build --target development --no-cache --tag grants-ui-backend:development .Run:
docker run \
-e PORT=3001 \
-p 3001:3001 \
grants-ui-backend:developmentSet any additional environment variables required by your deployment (see Environment configuration).
Build:
docker build --no-cache --tag grants-ui-backend .Run:
docker run \
-e PORT=3001 \
-p 3001:3001 \
grants-ui-backendSet any additional environment variables required by your deployment (see Environment configuration).
For day-to-day development, follow Docker Compose (recommended). The service runs in the foreground by default. To run in detached mode (background), add the -d flag:
docker compose up --build -dDependabot is configured for this repository in .github/dependabot.yml
Instructions for setting up SonarCloud can be found in sonar-project.properties
The project includes a Postman collection to make it easier to test and interact with the API. This collection contains pre-configured requests for various endpoints and an environment file to manage variables like API URLs.
-
Install Postman If you don’t already have Postman installed, download it from Postman’s official site.
-
Import the Collection
- Open Postman.
- Go to File > Import.
- Select the file
postman/grants-ui-backend.postman_collection.json.
-
Import the Environment (Optional) If the project includes an environment file:
- Go to File > Import.
- Select the file
postman/grants-ui-backend.dev.postman_environment.json. - Update variables like
base_url,api_keyorgrant_typeas needed.
-
Set the Active Environment
- In Postman, click on the environment dropdown in the top right corner.
- Select the imported environment (e.g.,
dev).
The API uses AES-256-GCM encrypted tokens with Bearer Authentication.
- Setup your plain, secret value (GRANTS_UI_BACKEND_AUTH_TOKEN) and encrypt/decrypt key (GRANTS_UI_BACKEND_ENCRYPTION_KEY) in your environment variables (see
env.example.shfor reference):
GRANTS_UI_BACKEND_AUTH_TOKEN=<your token>
GRANTS_UI_BACKEND_ENCRYPTION_KEY=<your encryption key>
- Use the npm script to generate the Bearer token:
npm run generate:auth-headerCopy the output Authorization: Bearer ... header and use it in Postman under the grants-ui-backend-bearer_token in Environments tab for your requests.
Some requests require a Lock Token (state operations) to acquire or release an application lock. This header is generated locally using a Node script. The admin delete application lock does not require lock token.
For local testing with Postman, set these environment variables in your .env file (these are separate from the backend service configuration):
APPLICATION_LOCK_TOKEN_SECRET=<64-character hex key>
USER_ID=<user-id-of-the-user-holding-the-token>
SBI=<sbi-holding-the-token>
GRANT_CODE=<grant-code-holding-the-token>
GRANT_VERSION=<grant-version-holding-the-token>
Note: APPLICATION_LOCK_TOKEN_SECRET must match the value configured in the backend service. The other variables (USER_ID, SBI, GRANT_CODE, GRANT_VERSION) are only needed for generating test tokens and should match the application you're testing.
Run the npm script:
npm run generate:lock-header
This outputs a header in the format:
x-application-lock-owner: <lock-token>
Copy this token and include it in your Postman requests to endpoints that require application locks.
Script location:
scripts/generateLockHeader.js
In addition to TTL-based expiry, application locks may be explicitly released when a user signs out or otherwise exits the application flow.
Lock release is performed via a dedicated endpoint and does not use the application-scoped lock token.
Lock release requests must include a JWT in the x-application-lock-release header.
This token:
- Identifies the user only (
ownerId) - Is not scoped to a specific application
- Is used solely for releasing locks owned by that user
- Has a distinct token type (e.g.
typ: 'lock-release')
When a valid lock-release token is presented, the backend:
- Verifies the token and extracts
ownerId - Deletes all active application locks owned by that user
- Returns the number of released locks
This operation is idempotent.
Note: Lock release tokens are intentionally distinct from application lock tokens and must not be used for lock acquisition or enforcement.
Run the npm script:
npm run generate:lock-release-header
This outputs a header in the format:
x-application-lock-release: <release-token>
Use this header when calling:
DELETE /application-locks
This endpoint releases all application locks held by the authenticated user, typically during sign-out.
Script location:
scripts/generateLockReleaseHeader.js
-
Send Requests: Once imported, you can navigate through the requests in the collection and send them directly to the API.
-
Customize Variables: If using an environment file, adjust variables like
base_urlto match your local or deployed API instance.
The Postman collection is maintained in the repository under the /postman/ directory. If the API changes, the collection will be updated accordingly. Pull the latest changes from the repository to ensure you have the most up-to-date collection.
project-root/
├── postman/
│ ├── grants-ui-backend.postman_collection.json
│ ├── grants-ui-backend.local.postman_environment.json
│ ├── grants-ui-backend.dev.postman_environment.json
│ └── grants-ui-backend.test.postman_environment.json
├── scripts/
│ ├── generateAuthHeader.js
│ ├── generateLockHeader.js
│ └── generateLockReleaseHeader.js
THIS INFORMATION IS LICENSED UNDER THE CONDITIONS OF THE OPEN GOVERNMENT LICENCE found at:
http://www.nationalarchives.gov.uk/doc/open-government-licence/version/3
The following attribution statement MUST be cited in your products and applications when using this information.
Contains public sector information licensed under the Open Government license v3
The Open Government Licence (OGL) was developed by the Controller of Her Majesty's Stationery Office (HMSO) to enable information providers in the public sector to license the use and re-use of their information under a common open licence.
It is designed to encourage use and re-use of information freely and flexibly, with only a few conditions.