Deploy Express API | TypeScript, Rate Limiting and Security Headers
Express 5 and TypeScript, proxy-aware rate limiting and security headers
Just deployed
Deploy and Host an Express API on Railway
Express 5 on TypeScript, with the middleware an internet-facing API actually needs.
About Hosting Express
The ExpressJS template on Railway deploys a repository whose package.json is a 2018 express-generator skeleton: express ~4.16.1, pug 2.0.0-beta11, debug ~2.6.9, http-errors ~1.6.3. Express 4.16 shipped in 2017 and that pug is a beta from the same year. Around four deployments in ten do not come up, and the ones that do run a stack nobody has patched in seven years.
This starter is Express 5 on current Node, with a committed lockfile that passes npm audit --audit-level=high - which matters here, because Railway refuses to build when the lockfile carries a HIGH advisory.
Common Use Cases
- A JSON API for a front end you host separately
- A webhook receiver that has to be reachable and rate-limited
- A starting point that already handles proxies, CORS and shutdown correctly
Dependencies for Express Hosting
- Nothing. One service, no database. Add Prisma or Mongoose and a database service when you need one.
Deployment Dependencies
- Express 5: https://expressjs.com
- Source: https://github.com/ak40u/express-api-railway-starter
Implementation Details
| Method | Path | Does |
|---|---|---|
| GET | / | Lists the endpoints |
| GET | /health | Status and uptime - deliberately outside the rate limiter |
| GET | /api/time | Server time |
| POST | /api/echo | Echoes {"message": "..."}, 400 when missing |
Four decisions, each of which is a thing that bites in production rather than locally:
- app.set("trust proxy", 1). TLS terminates at the platform proxy and the real client address arrives in X-Forwarded-For. Without this every request appears to come from the proxy, which breaks rate limiting and any per-client logic. The value 1 trusts exactly one hop; trusting all of them would let a client spoof its own address.
- Rate limiting covers /api, not everything. A burst of traffic should not lock out the health check and cause the platform to replace a container that is working fine.
- helmet() is on. The security headers a browser-facing API is expected to send, with no reason to omit them.
- Graceful shutdown. SIGTERM arrives before a container is replaced; without a handler, in-flight requests are cut off on every deploy.
Why Deploy Express on Railway?
Push TypeScript, get a running API on a domain with TLS and a health check - and a middleware stack that behaves correctly behind a proxy, which is the part most starters get wrong.
Template Content