How to set up CI/CD pipeline for Node.js
Setting up CI/CD pipelines for Node.js automates testing, building, and deployment, ensuring code quality and faster releases. As the creator of CoreUI with over 12 years of Node.js experience since 2014, I’ve configured numerous CI/CD pipelines for production applications. CI/CD pipelines automatically run tests, build artifacts, and deploy to servers whenever code changes are pushed. This approach catches bugs early, maintains code quality, and enables frequent, reliable deployments.
Configure automated CI/CD pipeline with testing, building, and deployment stages for Node.js applications.
Basic CI/CD workflow structure:
# .github/workflows/ci.yml (GitHub Actions example)
name: Node.js CI/CD
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [18.x, 20.x]
steps:
- uses: actions/checkout@v3
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run linter
run: npm run lint
- name: Run tests
run: npm test
- name: Run test coverage
run: npm run test:coverage
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
with:
token: ${{ secrets.CODECOV_TOKEN }}
build:
needs: test
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v3
- name: Use Node.js
uses: actions/setup-node@v3
with:
node-version: 20.x
- name: Install dependencies
run: npm ci
- name: Build application
run: npm run build
- name: Archive build artifacts
uses: actions/upload-artifact@v3
with:
name: build
path: dist/
deploy:
needs: build
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v3
- name: Download build artifacts
uses: actions/download-artifact@v3
with:
name: build
path: dist/
- name: Deploy to production
run: |
echo "Deploying to production server"
# Add deployment commands here
Package.json scripts for CI/CD:
{
"name": "nodejs-app",
"version": "1.0.0",
"scripts": {
"start": "node dist/index.js",
"dev": "nodemon src/index.js",
"build": "tsc",
"test": "jest",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage",
"lint": "eslint src/**/*.js",
"lint:fix": "eslint src/**/*.js --fix",
"format": "prettier --write \"src/**/*.js\"",
"typecheck": "tsc --noEmit",
"ci": "npm run lint && npm run test && npm run build"
}
}
Jest configuration for CI:
// jest.config.js
module.exports = {
testEnvironment: 'node',
coverageDirectory: 'coverage',
collectCoverageFrom: [
'src/**/*.js',
'!src/**/*.test.js',
'!src/**/__tests__/**'
],
coverageThreshold: {
global: {
branches: 80,
functions: 80,
lines: 80,
statements: 80
}
},
testMatch: [
'**/__tests__/**/*.js',
'**/?(*.)+(spec|test).js'
],
verbose: true,
ci: true,
bail: true
}
Docker multi-stage build:
# Dockerfile
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build
# Production image
FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/package*.json ./
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/dist ./dist
ENV NODE_ENV=production
EXPOSE 3000
USER node
CMD ["node", "dist/index.js"]
GitHub Actions with Docker:
# .github/workflows/docker.yml
name: Docker Build and Push
on:
push:
branches: [main]
tags:
- 'v*'
jobs:
docker:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Login to DockerHub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v4
with:
images: username/nodejs-app
tags: |
type=ref,event=branch
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
- name: Build and push
uses: docker/build-push-action@v4
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
Environment-specific deployments:
# .github/workflows/deploy.yml
name: Deploy
on:
push:
branches:
- main
- staging
jobs:
deploy-staging:
if: github.ref == 'refs/heads/staging'
runs-on: ubuntu-latest
environment: staging
steps:
- uses: actions/checkout@v3
- name: Deploy to staging
env:
API_URL: ${{ secrets.STAGING_API_URL }}
DATABASE_URL: ${{ secrets.STAGING_DATABASE_URL }}
run: |
echo "Deploying to staging"
npm run deploy:staging
deploy-production:
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
environment: production
steps:
- uses: actions/checkout@v3
- name: Deploy to production
env:
API_URL: ${{ secrets.PROD_API_URL }}
DATABASE_URL: ${{ secrets.PROD_DATABASE_URL }}
run: |
echo "Deploying to production"
npm run deploy:prod
Security scanning:
# .github/workflows/security.yml
name: Security Scan
on:
push:
branches: [main]
schedule:
- cron: '0 0 * * 0'
jobs:
audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Run npm audit
run: npm audit --audit-level=high
- name: Run Snyk security scan
uses: snyk/actions/node@master
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
scan-type: 'fs'
scan-ref: '.'
format: 'sarif'
output: 'trivy-results.sarif'
- name: Upload Trivy results to GitHub Security
uses: github/codeql-action/upload-sarif@v2
with:
sarif_file: 'trivy-results.sarif'
Performance testing in CI:
# .github/workflows/performance.yml
name: Performance Tests
on:
push:
branches: [main]
jobs:
performance:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: 20.x
- name: Install dependencies
run: npm ci
- name: Start application
run: npm start &
env:
NODE_ENV: test
- name: Wait for application
run: npx wait-on http://localhost:3000
- name: Run load tests
run: npx artillery run tests/load-test.yml
- name: Run Lighthouse CI
uses: treosh/lighthouse-ci-action@v9
with:
urls: http://localhost:3000
uploadArtifacts: true
Database migrations in CI:
# .github/workflows/migrations.yml
name: Database Migrations
on:
push:
branches: [main]
jobs:
migrate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: 20.x
- name: Install dependencies
run: npm ci
- name: Run migrations
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
run: npm run migrate
- name: Seed database
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
run: npm run seed
Notification on deployment:
# .github/workflows/notify.yml
name: Deployment Notification
on:
deployment_status:
jobs:
notify:
if: github.event.deployment_status.state == 'success'
runs-on: ubuntu-latest
steps:
- name: Notify Slack
uses: slackapi/slack-github-action@v1
with:
payload: |
{
"text": "Deployment successful for ${{ github.event.deployment.environment }}",
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "✅ Deployment to *${{ github.event.deployment.environment }}* successful\nCommit: ${{ github.sha }}"
}
}
]
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }}
Best Practice Note
Run tests in CI on every push and pull request to catch bugs early. Use matrix builds to test against multiple Node.js versions. Cache dependencies with npm ci for faster builds. Fail the build if tests don’t meet coverage thresholds. Use environment secrets for sensitive configuration—never commit credentials. Run security audits regularly to catch vulnerabilities. Deploy automatically only after all tests pass. This is how we configure CI/CD for CoreUI Node.js backends—automated testing, building, and deployment with security scanning, ensuring reliable releases and maintaining code quality in production applications.



