Quality Gates
Block merges and deploys until lint, tests, typecheck, and security audit pass on Node.js 24 TypeScript code.
Recipe
Quick-reference recipe card - copy-paste ready.
steps:
- run: npm ci
- run: npm run lint
- run: npm run typecheck
- run: npm run test
- run: npm audit --audit-level=high
- run: npm run buildWhen to reach for this: Before any deploy to staging or production. Quality gates are non-negotiable for production Node APIs.
Working Example
{
"scripts": {
"lint": "eslint src --max-warnings 0",
"typecheck": "tsc -p tsconfig.json --noEmit",
"test": "vitest run --coverage",
"test:unit": "vitest run --project unit",
"test:integration": "vitest run --project integration",
"build": "tsc -p tsconfig.json",
"audit:ci": "npm audit --audit-level=high",
"gates": "npm run lint && npm run typecheck && npm run test && npm run audit:ci && npm run build"
}
}# .github/workflows/ci.yml (excerpt)
jobs:
gates:
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 gates
docker-scan:
needs: gates
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: docker build -t api:ci .
- uses: aquasecurity/trivy-action@master
with:
image-ref: api:ci
severity: CRITICAL,HIGH
exit-code: 1What this demonstrates:
- Single
gatesscript local developers run before push --max-warnings 0treats lint warnings as failures- Trivy scan on Docker image after application gates pass
Deep Dive
Gate Ordering and Cost
| Gate | Typical duration | Fail fast? |
|---|---|---|
| lint | 5-30s | Yes |
| typecheck | 10-60s | Yes |
| unit test | 1-5 min | Yes |
| integration test | 2-15 min | After unit |
| npm audit | 5-20s | Yes (policy-dependent) |
| docker build + scan | 2-10 min | Release only |
ESLint Gate
// eslint.config.js excerpt
export default [
{
rules: {
"no-console": "error",
"@typescript-eslint/no-floating-promises": "error",
},
},
];Ban console.log in src/; use structured logging.
Typecheck Gate
{
"scripts": {
"typecheck": "tsc -p tsconfig.json --noEmit"
}
}Run in CI even if build emits types. --noEmit is faster for PR feedback.
Coverage Threshold (Optional)
// vitest.config.ts
export default {
test: {
coverage: {
thresholds: {
lines: 80,
functions: 80,
branches: 70,
},
},
},
};Start with diff coverage on changed files if full-repo thresholds are too harsh initially.
Branch Protection
Configure in GitHub:
- Require status check
gates(anddocker-scanfor release branches) - Require PR review
- Dismiss stale approvals on new commits
Gotchas
npm auditnoise on transitive deps - blocks every PR. Fix:audit-level=high, Renovate for bumps, ornpm audit fixplaybook.- Skipping typecheck when
buildrunstsc- build may skip strict files. Fix: explicittypecheckjob. - Lint only changed files in CI - main branch drifts. Fix: lint entire
src/on every run (fast enough). - Integration tests without service containers - flaky CI. Fix: postgres/redis service containers in workflow.
- Docker scan only on main - vulnerable Dockerfile on PR merges. Fix: scan on PR when
Dockerfilechanges. - No lockfile -
npm cifails or audit is meaningless. Fix: enforcepackage-lock.jsonin repo.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
| Strict gates on every PR | Production APIs | Early prototype repos (temporary) |
| Nightly full integration | Slow E2E suites | You skip all integration on PR |
| SonarQube / CodeClimate | Org-wide quality metrics | Small team with ESLint + Vitest enough |
| Snyk / Dependabot | CVE triage automation | Replacing npm audit entirely without policy |
FAQs
Should gates run on docs-only PRs?
Use path filters to skip CI when only *.md changes, but keep filters narrow. Most Node PRs touch code.
What audit level for regulated environments?
Fail on moderate or above and maintain SBOM export (npm sbom) in release pipeline.
Do quality gates replace code review?
No. They catch mechanical issues; humans catch design and security logic flaws.
How do gates apply to Lambda?
Same gates script before esbuild zip. Scan zip with trivy fs if not using Docker.
Monorepo: one gate job or many?
Affected-package detection (Nx/Turbo) per service, but never skip gates on a service whose code changed.
Can we deploy if staging smoke fails?
No. Smoke test is a gate between staging and production - CI/CD Basics.
Related
- CI/CD Basics - pipeline structure
- GitHub Actions for Node - workflow wiring
- Typecheck in CI - TypeScript gates
- Linting Basics - ESLint setup
- CI/CD Best Practices - section checklist
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.