# Deploying an Express App

Course: [Express.js](https://www.devtutorials.ai/learn/expressjs) · Section: Operations
Source: https://www.devtutorials.ai/learn/expressjs/operations/deploying-express

> 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.

## 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.

```bash
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.

```dockerfile
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

<div className="tscroll">

| Mistake | Wrong idea | Correct idea | What goes wrong |
|---|---|---|---|
| Hard-coded port 3000 | Every host uses 3000 | `process.env.PORT ?? 3000` | The host cannot reach the app |
| Committing `.env` | It is just settings | Keep secrets in the host's dashboard | Secrets leak on GitHub |
| Forgetting `NODE_ENV=production` | It only changes logs | It caches views and hides error details | Slow pages and leaked stack traces |
| Checking config late | It will crash if needed | Check every required value at startup | The app breaks on the first real request |
| No SIGTERM handler | Stopping is instant | `server.close()` and then exit | Requests are cut off on every deploy |
| Liveness check uses the database | More checks are safer | Liveness stays simple, readiness checks the database | A slow database restarts healthy apps |
| `nodemon` in production | It keeps the app running | Use a process manager or the host | Restarts on file changes, not crashes |

</div>

## 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.
