Skip to content

DEFRA/grants-ui-backend

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

214 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

grants-ui-backend

Core delivery platform Node.js Backend Template.

Related documentation

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:

Use this README for backend-specific setup; refer to the frontend README when you need context about journeys, shared tooling, or logging standards.

Requirements

Node.js

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 use

Local development

Docker Compose (recommended)

For a self-contained local environment (service plus MongoDB), use the provided Compose file:

docker compose up --build

This 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 down

To spin up the full stack with the frontend, follow the Docker guidance in grants-ui.

Local Node environment

If you prefer to run the application directly on your machine, follow the steps below.

Setup

Install application dependencies:

npm install

Environment configuration

Create your environment configuration file. You can use the provided example as a template:

cp env.example.sh .env

Required 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 with openssl rand -hex 32)
  • GRANTS_UI_BACKEND_ENCRYPTION_KEY – 64 character lowercase hexadecimal string (generate with openssl rand -hex 32)
  • APPLICATION_LOCK_TOKEN_SECRET – 64 character lowercase hexadecimal string (generate with openssl 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 with openssl 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.

Development

To run the application in development mode run:

npm run dev

Testing

To test the application run:

npm run test

Integration tests rely on Docker (via Testcontainers) and can be run with:

npm run test:integration

The frontend documents complementary UI testing patterns in its testing framework section.

Git hooks

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:husky to reinstall the hooks
  • address formatting or lint failures with npm run lint:fix
  • fix failing tests locally before committing

Production

To mimic the application running in production mode locally run:

npm start

Npm scripts

All available Npm scripts can be seen in package.json. To view them in your command line run:

npm run

Common development scripts:

  • npm run dev – Run the application in development mode with auto-reload
  • npm run dev:debug – Run in development mode with debugger breakpoint support
  • npm start – Run the application in production mode
  • npm test – Run unit tests with coverage
  • npm run test:watch – Run tests in watch mode
  • npm run test:integration – Run integration tests (requires Docker)
  • npm run lint – Check code for linting errors
  • npm run lint:fix – Automatically fix linting errors
  • npm run format – Auto-format code with Prettier
  • npm run format:check – Check code formatting without making changes
  • npm run generate:auth-header – Generate Bearer token for API authentication
  • npm run generate:lock-header – Generate lock token for application lock-protected routes
  • npm run generate:lock-release-header – Generate lock release token for application lock release route

Update dependencies

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 group

Formatting

Windows prettier issue

If you are having issues with formatting of line breaks on Windows update your global git config by running:

git config --global core.autocrlf false

OpenAPI Specification

An OpenAPI 3.1 specification is available at the repository root:

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.

Development helpers

Structured logging

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.

Application state and frontend rehydration

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.

Mongo configuration

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.

Application locking

This service enforces exclusive application access to grant applications using a MongoDB-backed locking mechanism.

Locks are scoped to a single application, identified by:

  • grantCode
  • grantVersion
  • sbi (Single Business Identifier)

Only one user from a given business may view/edit a given application at a time.

How locking works

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:

  1. Extracts and validates the lock token from the x-application-lock-owner header
  2. Attempts to acquire or refresh a lock for the authenticated user
  3. Refreshes the lock if the same user already holds it
  4. 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 Identifier
  • grantCode – Grant application code
  • grantVersion – Grant scheme version
  • typ – Token type (must be 'lock')

Generate lock tokens using npm run generate:lock-header (see Generating an Application Lock Header).

Enforcement

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

Storage

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.

Notes

  • 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.

Proxy

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.

Docker

Development image

Build:

docker build --target development --no-cache --tag grants-ui-backend:development .

Run:

docker run \
  -e PORT=3001 \
  -p 3001:3001 \
  grants-ui-backend:development

Set any additional environment variables required by your deployment (see Environment configuration).

Production image

Build:

docker build --no-cache --tag grants-ui-backend .

Run:

docker run \
  -e PORT=3001 \
  -p 3001:3001 \
  grants-ui-backend

Set any additional environment variables required by your deployment (see Environment configuration).

Docker Compose

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 -d

Dependabot

Dependabot is configured for this repository in .github/dependabot.yml

SonarCloud

Instructions for setting up SonarCloud can be found in sonar-project.properties

Postman Collection

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.

Getting Started

  1. Install Postman If you don’t already have Postman installed, download it from Postman’s official site.

  2. Import the Collection

    • Open Postman.
    • Go to File > Import.
    • Select the file postman/grants-ui-backend.postman_collection.json.
  3. 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_key or grant_type as needed.
  4. Set the Active Environment

    • In Postman, click on the environment dropdown in the top right corner.
    • Select the imported environment (e.g., dev).

Service-to-Service Authentication

The API uses AES-256-GCM encrypted tokens with Bearer Authentication.

Generating the Authorization Header

  1. 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.sh for reference):
GRANTS_UI_BACKEND_AUTH_TOKEN=<your token>
GRANTS_UI_BACKEND_ENCRYPTION_KEY=<your encryption key>
  1. Use the npm script to generate the Bearer token:
npm run generate:auth-header

Copy the output Authorization: Bearer ... header and use it in Postman under the grants-ui-backend-bearer_token in Environments tab for your requests.

⚠️ Make sure the environment variables match what the backend config expects.

Generating an Application Lock Header

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.

Environment variables

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.

Generate the Lock Token

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

Generating an Application Lock Release Header

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 authentication

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')

Release semantics

When a valid lock-release token is presented, the backend:

  1. Verifies the token and extracts ownerId
  2. Deletes all active application locks owned by that user
  3. 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.

Generate the Lock Release Token (Sign-out)

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

Usage

  • 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_url to match your local or deployed API instance.

Keeping the Collection Updated

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.

Example Folder Structure


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

Licence

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

About the licence

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.

About

Git repository for service grants-ui-backend

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages