CI/CD Basics
10 examples to structure CI (pull request validation) and CD (release deploy) for Node.js 24 TypeScript services - 7 basic and 3 intermediate.
Search across all documentation pages
10 examples to structure CI (pull request validation) and CD (release deploy) for Node.js 24 TypeScript services - 7 basic and 3 intermediate.
mkdir node-service && cd node-service
npm init -y
npm pkg set type=module
npm install express@5
npm install -D typescript@5.6 vitest eslint @types/nodeFor GitHub Actions specifics, see GitHub Actions for Node and Quality Gates.
PR opened / push
-> install (npm ci)
-> lint
-> typecheck
-> unit test
-> (optional) integration test
-> block merge if any step fails
Tag / merge to main
-> install + test (repeat gates)
-> build (tsc / docker build)
-> scan image / audit
-> push artifact (registry / S3 zip)
-> deploy staging
-> smoke test staging
-> deploy production (manual approval or progressive)
package.json CI Scripts{
"scripts": {
"lint": "eslint src",
"typecheck": "tsc --noEmit",
"test": "vitest run",
"build": "tsc -p tsconfig.json",
"audit:ci": "npm audit --audit-level=high"
}
}tsc --noEmit catches type errors without emitting filespackage.json devDependencies# .github/workflows/ci.yml
name: CI
on:
pull_request:
push:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "24"
cache: npm
- run: npm ci
- run: npm run lint
- run: npm run typecheck
- run: npm run testcache: npm speeds installs when lockfile unchangedpull_request and push to mainsteps:
- run: npm ci
- run: npm run lint # seconds
- run: npm run typecheck # seconds
- run: npm run test # minutes
- run: npm run build # after tests pass| Environment | Trigger | Purpose |
|---|---|---|
ci | Every PR | Validation only |
staging | Merge to main | Pre-prod integration |
production | Tag or approved workflow | Customer traffic |
- name: Build and push image
run: |
IMAGE=ghcr.io/acme/api:${{ github.sha }}
docker build -t "$IMAGE" .
docker push "$IMAGE"github.sha, not :latest alone# .github/workflows/reusable-node-ci.yml
on:
workflow_call:
inputs:
node-version:
required: false
type: string
default: "24"
jobs:
ci:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ inputs.node-version }}
cache: npm
- run: npm ci
- run: npm run lint && npm run typecheck && npm run test# .github/workflows/ci.yml
jobs:
call-ci:
uses: ./.github/workflows/reusable-node-ci.yml# .github/workflows/release.yml
name: Release
on:
push:
tags: ["v*.*.*"]
jobs:
deploy-staging:
runs-on: ubuntu-latest
environment: staging
steps:
- uses: actions/checkout@v4
- run: npm ci && npm test && npm run build
- run: ./scripts/deploy.sh staging ${{ github.sha }}
deploy-production:
needs: deploy-staging
runs-on: ubuntu-latest
environment: production
steps:
- run: ./scripts/deploy.sh production ${{ github.sha }}environment adds required reviewers for production- name: Validate commit messages on PR
uses: wagoid/commitlint-github-action@v6Optional preview environments per PR - Preview Environments. Default: validate only, deploy on merge.
Yes. Re-run gates on main to catch merge skew. Some teams trust PR-only; re-running on main is safer.
Release pipeline after tests, or PR pipeline only for Dockerfile changes with scan gate. Never skip tests before image push.
Path filters: paths: ['services/api/**'] trigger only affected workflows. Use Turborepo or Nx for affected detection.
CI builds zip artifact; CD runs aws lambda update-function-code with the same SHA-named S3 object.
GitHub Actions secrets and OIDC to AWS. No long-lived AWS keys in repo. See Secrets Managers.
Stack versions: This page was written for Node.js 24.18.0 (Active LTS), npm 10+, TypeScript 5.6+, Express 5, Fastify 5, and NestJS 11.
Reviewed by Chris St. John·Last updated Jul 19, 2026