June 22, 2026/3 min read/Admin

Making a Node.js and Prisma API Production-Ready

The unglamorous work that turns a working Express + Prisma API into a production-ready service: migrations, graceful shutdown, error mapping, health checks and Docker.

nodejsexpressprismatypescriptdockerbackenddevops

"It works on my machine" and "it is production-ready" are very different statements. Here is the checklist I worked through to take an Express + Prisma + MySQL API from the first to the second — using this very portfolio backend as the guinea pig.

1. Real migrations, not db push

prisma db push is great for prototyping and dangerous in production — there is no history and no safe rollback. The fix is committed migrations applied on deploy:

npx prisma migrate dev --name init   # generate + commit
npx prisma migrate deploy            # run on startup (never prompts)

If the database already has tables but no migration history you will hit P3005. Baseline it once by marking the migration as already applied, then deploys are clean forever after.

2. Graceful shutdown

Containers get SIGTERM on every redeploy. Without handling it you drop in-flight requests and leak database connections. Close the server, then disconnect Prisma:

process.on('SIGTERM', async () => {
  server.close(async () => {
    await prisma.$disconnect();
    process.exit(0);
  });
});

3. Map errors to real status codes

A blanket 500 tells the client nothing. A small helper maps known Prisma errors — unique violation to 409, record-not-found to 404 — and keeps the raw error server-side only.

4. Liveness vs readiness

Two different questions. /health answers "is the process up?" cheaply. /health/ready answers "can I serve traffic?" by actually pinging the database with SELECT 1 — that is what your load balancer should probe.

5. Config and secrets

  • Fail fast at startup if a required secret like JWT_SECRET is missing.
  • Never ship a hardcoded admin password — seed a random one and print it once.
  • Keep .env out of git; commit a .env.example template instead.

6. A lean Docker image

A multi-stage build compiles TypeScript in a builder stage and ships only the runtime deps and compiled output, running as a non-root user. Migrations run as the first thing the container does on boot.

None of this is exciting, and that is the point. Production-ready means boring, predictable and observable.

Share this article

Help others discover this content.