GitHub Actions Integration
A cookbook for GitHub Actions + branch protection on Node.js teams - required checks, tag-driven releases, and PR templates that catch migration and rollback gaps before merge.
Recipe
Quick-reference recipe card - copy-paste ready.
# .github/workflows/pr-checks.yml
name: PR Checks
on:
pull_request:
branches: [main]
jobs:
quality:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "24.18.0"
cache: npm
- run: npm ci
- run: npm run typecheck
- run: npm run lint
- run: npm test -- --ci
- run: npm audit --audit-level=high# .github/workflows/release.yml
name: Release
on:
push:
tags: ["v*"]
jobs:
build-and-push:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "24.18.0"
cache: npm
- run: npm ci
- run: npm run build
- run: npm test -- --ci
# docker build + push to registry<!-- .github/pull_request_template.md -->
## Summary
## Ticket
<!-- API-___ -->
## API / DB checklist
- [ ] OpenAPI or route list updated
- [ ] Migration included (or N/A)
- [ ] Rollback plan documented
- [ ] Worker compatibility checked (if event schema changed)
## CI
- [ ] `quality` check green locally: `npm ci && npm run typecheck && npm test`Branch protection (GitHub UI):
- Require status check:
quality - Require PR reviews: 1
- Require linear history or squash merge (team choice)
- Do not allow bypassing for
main
When to reach for this:
- First backend repo moving from "trust me I ran tests" to enforced gates
- Lockfile drift causing CI-only failures
- Untagged production deploys need semver traceability
- Monorepo needs path filters to skip unrelated services
Working Example
Step 1 - Match local and CI scripts
{
"scripts": {
"typecheck": "tsc --noEmit",
"lint": "eslint src test",
"test": "node --import tsx --test test/**/*.test.ts",
"build": "tsc -p tsconfig.build.json"
}
}# .husky/pre-push
npm run typecheck && npm testStep 2 - Monorepo path filter
on:
pull_request:
paths:
- "services/orders-api/**"
- "package-lock.json"
defaults:
run:
working-directory: services/orders-apiStep 3 - Migration gate (optional job)
db-lint:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:16
env:
POSTGRES_PASSWORD: postgres
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npm run db:migrate
env:
DATABASE_URL: postgres://postgres:postgres@localhost:5432/testStep 4 - Required checks in branch protection
Settings → Branches → main → Require status checks:
✓ quality
✓ db-lint (if migrations in PR)PR Labels (Optional)
| Label | Effect |
|---|---|
skip-audit | Docs-only; audit waived with security approval |
has-migration | Triggers db-lint job |
worker-change | Requires worker deploy note in template |
FAQs
npm audit fails on transitive dep we cannot fix?
Document exception in docs/security-audit-exceptions.md with expiry date. Do not permanently disable audit in CI.
Matrix Node 20 and 24 during upgrade?
Temporary matrix in PR checks during LTS migration. Remove N-2 version when fleet fully on 24.18.0.
Who can push tags?
Restrict tag creation to release managers or CI bot. Tags trigger production - protect with rulesets.
Related
- Git Basics for Node Teams - branch lanes and tags
- Git & GitHub Best Practices - portable summary
- GitHub Actions for Node - matrix and artifacts
- Package Managers Best Practices - npm ci policy
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.