Deploying an Express App

By Syncfusion®, Inc. · Last updated

Config from environment variables, failing fast, NODE_ENV=production, npm start and PM2, liveness and readiness checks, graceful shutdown on SIGTERM, Docker, hosting platforms and HTTPS.

What you will learn

  • Read config from the environment and fail fast
  • Run in production mode on the host's port
  • Add liveness and readiness checks
  • Shut down cleanly on SIGTERM
  • Package the app with Docker

Deploying an Express App

So far your server has run on your own computer. Deploying puts it on a server the whole school can reach.

  • Deploy = run your app on a computer that is always on and online.
  • Production = the real app that real users use. Development = your own computer.
  • Environment variables = settings such as the port and secrets, given to the app from outside the code.
  • Process manager = a tool that starts your app and restarts it if it crashes.
  • Health check = a small route the host calls to ask "are you OK?".
  • Graceful shutdown = finish current requests before stopping.

Simple lesson: Same code everywhere. Only the settings change between your laptop and production.

A Real-World Example

Think about opening a new canteen counter in the school.

  1. The recipes stay the same. Only the address and the staff list change. That is config.
  2. The manager checks every morning that the counter is open and has gas. That is a health check.
  3. If the cook falls sick, a new cook is called. That is a process manager restarting the app.
  4. At closing time, students already in the queue are still served. That is graceful shutdown.

In this lesson, you will learn:

  1. How to read config from environment variables, and fail fast when one is missing.
  2. What NODE_ENV=production changes.
  3. How to write npm start and health check routes.
  4. How to shut down cleanly on SIGTERM.
  5. How PM2, Docker, hosting platforms and HTTPS fit together.

Config from the Environment

Never hard-code ports, database addresses or secrets.

  • Read them from process.env. Hosts set these values for you.
  • PORT: most hosts tell your app which port to use. Default to 3000 on your computer.
  • NODE_ENV=production turns on view caching and hides error details in Express.
  • Put all config in one file, config.js, so the rest of the code never touches process.env.
  • Fail fast: if a required value is missing, stop at startup with a clear message. Do not crash later on the first request.
  • In production, /api/boom shows only Internal Server Error. The real message goes to the log, not the user.
  • On a host, you set JWT_SECRET in its dashboard. The .env file is for your computer only, and is never committed.
  • The app stops at once with Missing environment variable JWT_SECRET. The host shows the deploy as failed, so you fix it before users see anything.

Simple lesson: Config comes from the environment, is read in one file, and is checked at startup.

npm start and Process Managers

Every host runs your app with one command. Make it npm start.

  • "start": "node server.js" in package.json is the standard.
  • Add "engines": { "node": ">=22" } so the host picks the right Node.js version.
  • Commit package-lock.json. Hosts run npm ci to install exactly the same versions.
  • nodemon or node --watch restart on file changes. Use them only in development.
  • PM2 = a process manager for your own server. It restarts on crashes and can run one copy per CPU core.
npm install -g pm2
pm2 start server.js --name canteen-api -i max   # one copy per CPU core
pm2 logs canteen-api                            # read the logs
pm2 save && pm2 startup                         # start again after a reboot

Platforms such as Render or Railway restart crashed apps for you. You need PM2 only when you run your own server.

Simple lesson: One npm start command. A process manager keeps it running.

Health Checks

Hosts and load balancers call a route to check your app.

  • Liveness (/healthz): "Is the process alive?" Answer 200 fast. Do not touch the database.
  • Readiness (/readyz): "Can it serve users?" Check the database. Answer 503 if not ready.
  • If readiness fails, the host stops sending users to this copy but does not restart it.
  • If liveness fails many times, the host restarts the app.
  • Keep these routes out of your request logs and rate limits.

Simple lesson: /healthz says "alive". /readyz says "ready for users".

Graceful Shutdown

Hosts stop your app often: every new deploy, every scale-down.

  • First they send the SIGTERM signal. Then, after about 10 to 30 seconds, they force-kill it.
  • Listen with process.on("SIGTERM", ...).
  • Call server.close(). It stops new connections and waits for running requests to finish.
  • Then close the database and call process.exit(0).
  • Add a backup timer that exits anyway if closing takes too long.
  • Press Stop in a lab to send SIGTERM yourself.

Without a SIGTERM handler, every deploy can cut off orders halfway. A student pays, the server stops, and no order is saved.

Simple lesson: On SIGTERM, stop taking new work, finish current work, then exit.

Docker, Hosting and HTTPS

Docker

  • Docker image = your app plus Node.js, packed into one box that runs the same everywhere.
  • Use a small base image, install only production packages, and run as a normal user.
FROM node:22-slim
WORKDIR /app
ENV NODE_ENV=production
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
USER node
EXPOSE 3000
CMD ["node", "server.js"]
  • Add a .dockerignore with node_modules and .env, so secrets never enter the image.
  • CMD ["node", ...] (not npm start) lets SIGTERM reach your app directly.

Where to Host

  • Platforms (Render, Railway, Fly.io, Heroku): connect your Git repo, set env variables, done.
  • Containers (Google Cloud Run, AWS App Runner, Azure Container Apps): run your Docker image.
  • Your own server (a VPS): Node.js + PM2, with Nginx in front.
  • Serverless functions can run Express too, but cold starts and timeouts change how you write it.

HTTPS and Proxies

  • Users must reach your app over HTTPS. Platforms give you a certificate for free.
  • On your own server, Nginx or Caddy handles HTTPS and forwards to Express on port 3000.
  • Then set app.set("trust proxy", 1), so req.ip and req.secure are correct. See Redirects, Content Negotiation and Proxies.

Before going live, check: env variables set, NODE_ENV=production, HTTPS on, health checks passing, SIGTERM handled, logs readable, and a database backup plan.

Simple lesson: Pack the app the same way every time, put HTTPS in front, and let the host restart it.

Common mistakes

MistakeWrong ideaCorrect ideaWhat goes wrong
Hard-coded port 3000Every host uses 3000process.env.PORT ?? 3000The host cannot reach the app
Committing .envIt is just settingsKeep secrets in the host's dashboardSecrets leak on GitHub
Forgetting NODE_ENV=productionIt only changes logsIt caches views and hides error detailsSlow pages and leaked stack traces
Checking config lateIt will crash if neededCheck every required value at startupThe app breaks on the first real request
No SIGTERM handlerStopping is instantserver.close() and then exitRequests are cut off on every deploy
Liveness check uses the databaseMore checks are saferLiveness stays simple, readiness checks the databaseA slow database restarts healthy apps
nodemon in productionIt keeps the app runningUse a process manager or the hostRestarts on file changes, not crashes

Check Yourself

Exercises

What to Remember

  • Read config from process.env in one file, and stop at startup if a required value is missing.
  • Run with NODE_ENV=production and listen on process.env.PORT.
  • npm start runs the app. PM2 or the host restarts it on crashes.
  • /healthz for liveness, /readyz with a database check for readiness.
  • On SIGTERM: server.close(), close the database, then process.exit(0).
  • Docker packs the app. HTTPS goes in front. Set trust proxy behind a proxy.

Next: Application Structure. You will organise a full Express project and test it.