The Marine Licensing Backend is part of a GDS-compliant replacement of the Marine Case Management System (MCMS).
Details available on the release version ticket
Details available on the release version ticket
Details available on the release version ticket
Details available on the release version ticket
Details available on the release version ticket
Details available on the release version ticket
Details available on the release version ticket
Release of the marine licensing backend that will be accessed by marine licensing frontend which will be used by the public. The link lists all the features within the application.
Initial release of the marine licensing backend platform.
The source code under src/ is split into domain-aligned folders:
| Folder | Description |
|---|---|
exemptions |
All API routes, models, and business logic for marine exemptions |
marine-licences |
All API routes, models, and business logic for marine licences |
shared |
Shared logic across both domains — config, helpers, plugins, routes, and services |
Please install Node.js >= v24.22.1 and npm >= v11. For the exact version used by this application, see .nvmrc. Minimum engine versions are also defined in package.json.
You may find it easier to manage Node.js versions using a version manager such
as nvm or n. From within the project folder you
can then either run nvm use or n auto to install the required version.
MongoDB >= 7.0 is required. The nearest-area policy fallback (see
docs/nearest-marine-plan-area-fallback.md)
uses database-level $documents aggregation with $geoNear inside $lookup,
which older servers do not support.
The composed MongoDB runs as a single-member replica set (rs0) so that
multi-document transactions work locally, matching the replica sets in all
deployed environments. The in-memory test server is also a single-member
replica set, with its binary version pinned in
.vite/mongo-memory-server.js to match the mongo image version in
compose.yml.
The replica set advertises its member as mongodb:27017, which only
resolves inside the Docker network. Clients running on the host (mongosh,
MongoDB Compass, npm run dev, npm run migrate:status) must skip topology
discovery by connecting with:
mongodb://localhost:27017/?directConnection=true
The replica set is initiated automatically by the mongodb service
healthcheck. Existing mongodb-data volumes survive the upgrade; if a local
instance ever ends up in a broken replica-set state, reset it with
docker compose down -v (this deletes local data).
Install application dependencies:
npm installTo run the application in development mode run:
npm run devTo 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 runDependabot automatically creates pull requests to update dependencies.
For authentication when running locally, there are 2 options. Whichever you use it has to match the option used by marine-licensing-frontend, so that auth tokens sent with requests to the backend are correctly validated:
The out-of-the-box config will use the cdp-defra-id-stub.
To set this up and run it, instructions are in marine-licensing-frontend. The .env.template file referred to by the instructions is in the root of this repo.
For most local development, you shouldn't need to override any of the env var defaults that are in config.js.
- Nearest marine plan area fallback — how a marine licence still gets policies when its sites intersect no marine plan area, and why the search is built the way it is.
- Scheduled jobs — how recurring jobs run on exactly one instance per scheduled fire, and the contract a job body has to meet to be safe under it.
API routes are defined under the api folders in each domain:
src/exemptions/api/src/marine-licences/api/src/shared/api/geo-parser/
Outside prod the service publishes an OpenAPI (Swagger 2.0) description of its
gateway routes (src/marine-licences/api/gateway-routes.js): the unauthenticated
routes used by Dynamics 365 and other non-frontend callers via the CDP gateway. The
unauthenticated /public/exemption/{id} and /public/marine-licence/{id} routes,
which the frontend calls, are not included.
| Endpoint | Description |
|---|---|
GET: /swagger.json |
OpenAPI specification |
GET: /documentation |
Redoc API documentation |
Locally, with the service running, open http://localhost:3001/documentation.
Neither endpoint is registered when ENVIRONMENT is prod; no configuration is
needed for this. The documented version is the deployed release (SERVICE_VERSION),
or local when that is not set.
A route appears in the spec only when its options include tags: ['api']. Documented
routes declare response.status Joi schemas; a response that does not match its schema
is still returned unchanged, and a warn with event.action
openapi:response-schema-mismatch is logged.
In deployed non-prod environments the docs are viewed through the CDP API Hub, e.g.
https://cdp-api-hub.dev.cdp-int.defra.cloud/hub/internal/marine-licensing-backend.
This requires the service's api_docs entry in cdp-tenant-config
("url": "/swagger.json", "doc_type": "openapi", "internal": true). The prod API
Hub entry shows "Docs unavailable" by design.
Build:
docker build --target development --no-cache --tag marine-licensing-backend:development .Run:
docker run -e PORT=3001 -p 3001:3001 marine-licensing-backend:developmentNote - the development image uses the source files directly using volumes, and will automatically rebuild to reflect any changes.
Build:
docker build --no-cache --tag marine-licensing-backend .Run:
docker run -e PORT=3001 -p 3001:3001 marine-licensing-backendDatabase migrations are automatically applied to mongo on application start.
npm run migrate:status
npm run migrate:up
npm run migrate:downSee migrations/README.md for full details
A local environment with:
- Floci for AWS services (S3, SQS)
- Redis
- MongoDB (single-member replica set — see MongoDB)
- marine-licensing-api-stub (must be checked out as a sibling directory) — stubs the ArcGIS marine-plan-policy lookup and the Dynamics contact details API. Policy wording still comes from the real GOV.UK marine-plans-explorer API.
- This service.
- A commented out frontend example.
docker compose up --build -dBy default the backend's marine-plan-policy lookup is pointed at the local stub. To hit the real
DEFRA ArcGIS FeatureServer instead, uncomment ARCGIS_FEATURE_SERVER_URL in your local .env
(see .env.template), then recreate the backend container:
docker compose up -d marine-licensing-backendRemove/blank the line and recreate again to switch back to the stub.
The "who is the exemption for" value comes from the Dynamics contacts API. There are no
Dynamics credentials locally, so docker compose up runs with DYNAMICS_ENABLED=true and
points the token endpoint and both contact details endpoints at the stub — the real code
path (token fetch → contacts GET → fullname) runs end to end against fake data.
The stub is seeded with the same five test users as the local CDP defra-id stub
(Sally Self, Jason Bourne, John Doe, John Silver), so the name shown is the name of the user
you logged in as. They are keyed on the registration's contactId — the value stored on
exemptions and sent to Dynamics — rather than the userId you type on the defra-id stub
login page, though the userId is accepted as an alias. Any other valid GUID resolves to a
placeholder named after itself (Test User 3fa85f64), so pre-existing seeded exemptions
still show something.
Under docker compose these values come from compose/dynamics-stub.env
rather than your local .env — compose's env_file wins over the .env the container loads
itself. To hit real Dynamics, replace the values in that file with the commented-out
DYNAMICS_* values from .env.template and recreate the backend container. When running the
backend outside docker (npm run dev), your local .env is what counts.
DYNAMICS_ENABLED also switches on the exemption submission queue poller and the
marine licence Dynamics SQS workers, and their endpoints are stubbed too: exemption
submit (DYNAMICS_API_URL), withdraw, update and marine licence submission. The stub
accepts any payload and returns 202, which is the only thing the backend checks — so
exemption queue items reach status: 'success' instead of retrying into
exemption-dynamics-queue-failed, and marine licences get
dynamicsOutbound.submit: 'sent'. Marine licence jobs travel over the
marine_licensing_d365_marine_licence queue created by compose/start-floci.sh.
The stub does not store submissions. To see what was sent, read its logs — each accepted submission is logged with the operation and exemption reference:
docker compose logs -f marine-licensing-api-stub | grep dynamics_submission_stub_requestInstructions for setting up SonarCloud can be found in sonar-project.properties
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.