Express.js β NHS Quickstart
If your team is JavaScript-first, Express is a tiny, reliable way to wrap SQL views and micro-tools with REST endpoints. Keep logic server-side, ship OpenAPI-lite endpoints, and front them with SSO.
Great for: Developer Β· Data Engineer (utility APIs) Β· Ops tools backing dashboards.
βοΈ 10-minute installβ
mkdir nhs-express && cd nhs-express
npm init -y
npm i express mssql dotenv cors helmet pino-http
Project layout
nhs-express/
.env
index.js
package.json
.env (local only β never commit)
SQLSERVER_SERVER=localhost
SQLSERVER_DATABASE=NHS_Analytics
PORT=3000
π βHello NHSβ API β read from a viewβ
- A) CommonJS (simplest)
- B) TypeScript (optional)
Create index.js:
const express = require('express')
const sql = require('mssql')
const cors = require('cors')
const helmet = require('helmet')
const pino = require('pino-http')()
require('dotenv').config()
const app = express()
app.use(helmet())
app.use(cors({ origin: false })) // adjust when calling from a browser origin
app.use(pino)
// Global connection pool (reused across requests)
const pool = new sql.ConnectionPool({
server: process.env.SQLSERVER_SERVER,
database: process.env.SQLSERVER_DATABASE,
options: { encrypt: true, trustServerCertificate: true }, // per Trust policy
authentication: { type: 'ntlm' } // or 'default' with user/password if needed
})
// Health
app.get('/healthz', (req, res) => res.json({ status: 'ok' }))
// Read-only KPI endpoint
app.get('/kpi', async (req, res) => {
try {
await pool.connect()
const r = await pool.request().query(`
SELECT TOP (50)
practice_id, total_appointments, attendance_rate, median_wait_minutes
FROM dbo.vw_PracticeKPI
ORDER BY total_appointments DESC
`)
res.json(r.recordset)
} catch (err) {
req.log.error(err)
res.status(500).json({ error: 'query_failed' })
}
})
const port = process.env.PORT || 3000
app.listen(port, () => console.log(`http://localhost:${port}`))
Run:
node index.js
Initialise TS:
npm i -D typescript ts-node @types/node @types/express
npx tsc --init --rootDir src --outDir dist --esModuleInterop
mkdir src && move index.js src/index.ts
π Security patternsβ
- A) Dev API key (simple)
- B) Azure AD JWT (production)
Use a shared header for development, then swap to Entra ID for production.
// apiKey.js
module.exports = function requireApiKey(req, res, next) {
const ok = req.header('x-api-key') && req.header('x-api-key') === process.env.API_KEY
if (!ok) return res.status(401).json({ error: 'unauthorised' })
next()
}
// usage:
// app.get('/kpi', requireApiKey, async (req,res)=>{ ... })
Validate bearer tokens issued by Entra ID (Azure AD). One approach is express-oauth2-jwt-bearer with Microsoft OpenID config.
npm i express-oauth2-jwt-bearer
// jwt.js
const { auth } = require('express-oauth2-jwt-bearer')
const requireJwt = auth({
issuerBaseURL: `https://login.microsoftonline.com/${process.env.AZURE_AD_TENANT_ID}/v2.0`,
audience: process.env.AZURE_AD_API_AUDIENCE // e.g. api://your-app-id
})
// usage:
// app.get('/kpi', requireJwt, async (req,res)=>{ ... })
CORS
// allow only your intranet origin (if a browser UI calls this API)
app.use(cors({
origin: ['https://intranet.yourtrust.nhs'],
methods: ['GET','POST'],
allowedHeaders: ['Content-Type','Authorization','x-api-key']
}))
π³ Docker (production-like)β
Dockerfile
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
ENV PORT=3000
EXPOSE 3000
CMD ["node", "index.js"]
Build & run:
docker build -t nhs-express .
docker run -p 3000:3000 --env-file .env nhs-express
Deploy to AWS App Runner or Azure App Service/Container Apps; keep networking private and front with SSO/WAF as required.
π Observability & resilienceβ
- Logs:
pino-httpoutputs JSON; ship to CloudWatch / Log Analytics. - Health:
/healthzendpoint; add DB ping if needed. - Shutdown: listen for SIGTERM and close the pool/server gracefully.
- Validation: whitelist/validate query params (e.g., with
zodorjoi).
π IG & safety checklistβ
- Use read-only DB roles for GET endpoints; separate accounts for writes.
- Apply small-number suppression for aggregates.
- Keep secrets in a secret store (Key Vault/Secrets Manager); never commit
.env. - Restrict CORS to known origins; protect
/docsor admin routes behind auth. - Record data usage and endpoint owners in the repo README.
π Measuring impactβ
- Latency: p95 for
/kpi. - Reliability: 5xx rate, uptime.
- Security: % endpoints protected; secrets from store.
- Adoption: number of dashboards/services calling the API.
π See alsoβ
Whatβs next?
Youβve completed the Learn β Express.js stage. Keep momentum: