Onboarding Path: Build and Deploy a Production-Ready Node.js Microservice
One-line learning outcome: Ship a small, tested, containerized Node.js microservice with CI/CD and basic observability.
Estimated total time: 10–14 hours (split across modules — suitable for 1–3 day onboarding or spread over a week)
Prerequisites: Comfortable with JavaScript/TypeScript basics, Git, command line, and HTTP concepts. Familiarity with Docker is helpful but not required.
Path overview (module-by-module)
- Module 1 — Project Setup & Git Workflow (1.0 hr) — difficulty: Easy
- Module 2 — API Design & Implementation (Express + TypeScript) (3.0 hrs) — difficulty: Medium
- Module 3 — Tests & Contract Verification (1.5 hrs) — difficulty: Medium
- Module 4 — Containerization & Local Deployment (1.5 hrs) — difficulty: Medium
- Module 5 — CI/CD Pipeline (1.5 hrs) — difficulty: Medium
- Module 6 — Observability & Health (1.5 hrs) — difficulty: Medium
- Mini project & wrap-up (1.0 hr) — difficulty: Medium
Module 1 — Project Setup & Git Workflow
Objective: Create a repository scaffold with TypeScript, ESLint, prettier, and a reproducible dev script.
Narrative: A consistent project layout and Git workflow reduces onboarding overhead. We'll initialize a repo, add standard configs, and enforce linting & formatting on commits.
Hands-on lab (commands & expected outputs)
# create project
mkdir orders-service && cd orders-service
npm init -y
npm install -D typescript ts-node-dev eslint prettier husky lint-staged
npx tsc --init
# expected: tsconfig.json created
# add sample package.json scripts (dev, build, start)
# set up husky pre-commit to run lint-staged
Exercises
- Initialize the repo and add a
src/index.tsthat logs "service started". Run withnpm run dev. - Add ESLint + Prettier and configure
lint-stagedto format.tsfiles on commit. - Optional harder: Add a GitHub Actions workflow that runs lint and TypeScript typecheck on pull request.
Mentor tip: Start with a minimal working setup; add rules only if they catch recurring issues.
Module 2 — API Design & Implementation (Express + TypeScript)
Objective: Implement a simple REST API for orders (CRUD), with request validation and typed controllers.
Narrative: Build a focused API endpoint set so the rest of the onboarding path has a concrete product to test, deploy, and observe.
Hands-on lab
# install server deps
npm install express express-validator cors
npm install -D @types/express
# src/app.ts
import express from 'express';
const app = express();
app.use(express.json());
let orders: { id: string; item: string }[] = [];
app.get('/orders', (req, res) => res.json(orders));
app.post('/orders', (req, res) => {
const id = String(Date.now());
const order = { id, item: req.body.item };
orders.push(order);
res.status(201).json(order);
});
export default app;
# src/server.ts
import app from './app';
app.listen(3000, () => console.log('Listening on 3000'));
# expected: server responds to GET /orders and POST /orders returning JSON
Exercises
- Add GET /orders/:id that returns 404 for missing ids.
- Validate POST /orders body to require a non-empty
item. Return 400 on invalid input. - Harder: Split controllers, services, and add a simple in-memory repository interface with dependency injection-friendly design.
Mentor tip: Emphasize small, testable functions over large handlers. Keep request validation near endpoints.
Module 3 — Tests & Contract Verification
Objective: Add unit and integration tests for handlers and API contracts.
Narrative: Tests protect refactors and serve as executable documentation for new hires.
Hands-on lab
# install test libs
npm install -D jest ts-jest supertest @types/jest
npx ts-jest config:init
# sample test (tests/orders.test.ts)
import request from 'supertest';
import app from '../src/app';
describe('orders API', () => {
it('creates and lists orders', async () => {
const res = await request(app).post('/orders').send({ item: 'coffee' });
expect(res.status).toBe(201);
const list = await request(app).get('/orders');
expect(list.body.length).toBeGreaterThan(0);
});
});
# run: npm test
# expected: tests pass
Exercises
- Write unit tests for validation logic returning 400 on invalid payloads.
- Mock the repository to test service logic in isolation.
- Harder: Add contract tests (Pact or schema tests) to validate the API surface.
Mentor tip: Show how failing tests provide focused debug info—encourage running a single test file during development.
Module 4 — Containerization & Local Deployment
Objective: Create a Dockerfile and run the service locally in a container.
Narrative: Containers make deployments consistent and local testing closer to production.
Hands-on lab
# Dockerfile (multi-stage)
FROM node:18-alpine AS build
WORKDIR /app
COPY package*.json .
RUN npm ci
COPY . .
RUN npm run build
FROM node:18-alpine
WORKDIR /app
COPY --from=build /app/dist ./dist
EXPOSE 3000
CMD ["node", "dist/server.js"]
# Build and run
docker build -t orders-service:local .
docker run -p 3000:3000 orders-service:local
# expected: service responds on localhost:3000
Exercises
- Build and run the image locally and exercise endpoints with curl.
- Optimize the Dockerfile (slim base image, shrink layers) to reduce size.
- Harder: Add Docker Compose to run service with a mocked PostgreSQL container.
Mentor tip: Teach how to inspect running containers (docker ps, docker logs) and map ports for easy testing.
Module 5 — CI/CD Pipeline
Objective: Configure a CI pipeline to run lint, tests, build, and publish a container image to a registry.
Narrative: Continuous integration ensures commits are validated; continuous delivery reduces manual steps to deploy.
Hands-on lab
# GitHub Actions workflow (simplified)
name: CI
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with: { 'node-version': '18' }
- run: npm ci
- run: npm test
- run: npm run build
# optional: build and push Docker image
Exercises
- Add a workflow that runs on PRs and reports status checks.
- Extend the workflow to build and push an image to Docker Hub or GitHub Container Registry on main branch.
- Harder: Add a deployment job that triggers Cloud Run or a Kubernetes rollout using a service account.
Mentor tip: Start with a fast feedback loop (lint + tests) before adding slower steps like building images.
Module 6 — Observability & Health
Objective: Add health checks, basic structured logging, and a metrics endpoint.
Narrative: Observability at a minimal level helps on-call triage and gives new hires visibility into system health.
Hands-on lab
# add endpoints
app.get('/health', (req, res) => res.json({ status: 'ok' }));
app.get('/metrics', (req, res) => res.send('# HELP orders_created_total ...'));
# structured logging example with pino
npm install pino
import pino from 'pino';
const logger = pino();
logger.info({ route: '/orders' }, 'handling request');
# expected: logs emitted as JSON, health endpoint returns status
Exercises
- Add a /health endpoint returning 500 when a dependency check fails (simulate dependency).
- Integrate a basic Prometheus exporter library and expose simple counters for created orders.
- Harder: Configure Fluentd/Logstash local stack or use a SaaS (Datadog, LogDNA) to ship logs.
Mentor tip: Encourage readable logs for humans during dev and structured logs for production analysis.
Mini project & wrap-up
Deliverable: Complete a small PR that implements pagination on GET /orders, includes tests, and passes CI. Deploy a new image to staging and verify /health and /metrics.
Testable checkpoints
- Repo initializes and npm scripts run (dev, build, test).
- API responds to CRUD requests locally and in Docker.
- CI pipeline runs on PR and passes.
- Health & metrics endpoints are reachable in deployed environment.
Sample solution sketches (kept separate from exercises)
Sketch: Controller functions are thin; business logic lives in a service module with an in-memory repo implementing an interface. Tests stub the repo. Dockerfile uses multi-stage build. CI runs npm ci, npm test, npm run build, and optionally builds/pushes images with a short-lived token.
Assessment rubric
- Automated checks (0–60): lint (10), tests (30), build (10), Docker image build (10).
- Manual review (0–40): code organization & readability (10), API design & validation (10), observability (10), CI config & docs (10).
Alternative language implementations & trade-offs
- Python (FastAPI): faster to write for some; has automatic docs; slower type safety unless using mypy.
- Go (net/http, Gin): produces single binary, small container images, strong concurrency models; steeper learning curve for newcomers.
Recommended reading & tooling
- 12 Factor App — best practices for cloud apps
- Node.js Best Practices repo
- Tools: Docker, GitHub Actions, Postman/HTTPie, pino, Prometheus/Alertmanager
Common stumbling blocks & debugging hints
- TypeScript config excludes src by default — ensure
tsconfig.jsonincludes your files. - Docker context copying node_modules — prefer npm ci in build stage to produce consistent installs.
- CI caching: cache node_modules between builds to speed up workflows.
Accessibility considerations
- Document API responses with examples and status codes; include alt text for diagrams.
- Ensure any demo UI (if added) uses semantic HTML and labels; provide captions for demo videos.
Finish by asking the new hire to pair with a mentor for a 1-hour code walkthrough and a checklist sign-off of the testable checkpoints.