Deploying & Monitoring REST APIs
Getting your REST API to production requires careful environment configuration, process management, and observability setup. This lesson covers Dockerization, cloud deployment, logging, and health checks.
1 Health Check Endpoint
JavaScript — Health & Readiness Checks
const mongoose = require("mongoose");
// Liveness probe — is the process alive?
app.get("/health", (req, res) => {
res.json({
status: "ok",
timestamp: new Date().toISOString(),
uptime: process.uptime(),
version: process.env.npm_package_version
});
});
// Readiness probe — is the app ready to serve traffic?
app.get("/ready", async (req, res) => {
const checks = {
database: mongoose.connection.readyState === 1 ? "ok" : "fail"
};
const allReady = Object.values(checks).every(v => v === "ok");
res.status(allReady ? 200 : 503).json({
status: allReady ? "ready" : "not ready",
checks
});
});
2 Dockerfile for Production
Dockerfile — Multi-Stage Build
# Stage 1: Install deps
FROM node:20-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
# Stage 2: Production image
FROM node:20-alpine
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY src/ ./src/
ENV NODE_ENV=production
USER node
EXPOSE 3000
HEALTHCHECK --interval=30s CMD wget -qO- http://localhost:3000/health || exit 1
CMD ["node", "src/server.js"]
YAML — docker-compose.yml
version: "3.9"
services:
api:
build: .
ports: ["3000:3000"]
environment:
- NODE_ENV=production
- MONGODB_URI=mongodb://mongo:27017/mydb
- JWT_SECRET=${JWT_SECRET}
depends_on: [mongo]
restart: unless-stopped
mongo:
image: mongo:7
volumes:
- mongo_data:/data/db
restart: unless-stopped
volumes:
mongo_data:
3 Structured Logging with Winston
JavaScript — Production Logging
const winston = require("winston");
const logger = winston.createLogger({
level: process.env.LOG_LEVEL || "info",
format: winston.format.combine(
winston.format.timestamp(),
winston.format.errors({ stack: true }),
winston.format.json() // structured JSON logs
),
transports: [
new winston.transports.Console(),
new winston.transports.File({ filename: "logs/error.log", level: "error" }),
new winston.transports.File({ filename: "logs/combined.log" })
]
});
// Request logger middleware
app.use((req, res, next) => {
const start = Date.now();
res.on("finish", () => {
logger.info("HTTP request", {
method: req.method, url: req.url,
status: res.statusCode,
duration: Date.now() - start + "ms",
ip: req.ip
});
});
next();
});
4 Code Challenge
Challenge: Deploy your REST API to Railway.app or Render.com (both have free tiers). Configure your environment variables through their dashboard, set up a MongoDB Atlas database, and verify your API is live at a public URL.