How to use GitHub Actions for Node.js apps
GitHub Actions automates Node.js workflows including testing, building, and deploying directly from your repository. With over 12 years of Node.js experience since 2014 and as the creator of CoreUI, I’ve configured GitHub Actions for numerous production projects. GitHub Actions provides built-in CI/CD with YAML-based workflows that run on various events like push, pull request, or schedule. This approach integrates testing and deployment seamlessly into your development workflow without external CI services.
Use GitHub Actions workflows to automate Node.js testing, building, and deployment with event-triggered pipelines.
Basic Node.js workflow:
# .github/workflows/nodejs.yml
name: Node.js CI
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [18.x, 20.x, 22.x]
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
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: Build
run: npm run build
Advanced workflow with caching:
# .github/workflows/test.yml
name: Test Suite
on: [push, pull_request]
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
node-version: [18.x, 20.x]
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- name: Get npm cache directory
id: npm-cache-dir
shell: bash
run: echo "dir=$(npm config get cache)" >> ${GITHUB_OUTPUT}
- name: Cache npm dependencies
uses: actions/cache@v3
with:
path: ${{ steps.npm-cache-dir.outputs.dir }}
key: ${{ runner.os }}-node-${{ matrix.node-version }}-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-${{ matrix.node-version }}-
${{ runner.os }}-node-
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test -- --ci --coverage --maxWorkers=2
- name: Upload coverage
uses: codecov/codecov-action@v3
with:
file: ./coverage/coverage-final.json
flags: unittests
name: codecov-${{ matrix.os }}-node-${{ matrix.node-version }}
Workflow with environment variables:
# .github/workflows/deploy.yml
name: Deploy to Production
on:
push:
branches: [main]
env:
NODE_ENV: production
APP_NAME: my-nodejs-app
jobs:
deploy:
runs-on: ubuntu-latest
environment: production
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20.x
- name: Install dependencies
run: npm ci
- name: Build application
env:
API_URL: ${{ secrets.API_URL }}
DATABASE_URL: ${{ secrets.DATABASE_URL }}
run: npm run build
- name: Run database migrations
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
run: npm run migrate
- name: Deploy to server
env:
SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
SERVER_HOST: ${{ secrets.SERVER_HOST }}
run: |
mkdir -p ~/.ssh
echo "$SSH_PRIVATE_KEY" > ~/.ssh/deploy_key
chmod 600 ~/.ssh/deploy_key
ssh -i ~/.ssh/deploy_key user@$SERVER_HOST 'cd /app && git pull && npm ci && pm2 restart all'
Conditional workflows:
# .github/workflows/conditional.yml
name: Conditional Workflows
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm test
deploy-staging:
needs: test
if: github.ref == 'refs/heads/develop' && github.event_name == 'push'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: echo "Deploying to staging"
deploy-production:
needs: test
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
runs-on: ubuntu-latest
environment: production
steps:
- uses: actions/checkout@v4
- run: echo "Deploying to production"
pr-checks:
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm run lint
- run: npm test
Monorepo workflow:
# .github/workflows/monorepo.yml
name: Monorepo CI
on: [push, pull_request]
jobs:
changes:
runs-on: ubuntu-latest
outputs:
api: ${{ steps.changes.outputs.api }}
web: ${{ steps.changes.outputs.web }}
steps:
- uses: actions/checkout@v4
- uses: dorny/paths-filter@v2
id: changes
with:
filters: |
api:
- 'packages/api/**'
web:
- 'packages/web/**'
test-api:
needs: changes
if: needs.changes.outputs.api == 'true'
runs-on: ubuntu-latest
defaults:
run:
working-directory: packages/api
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20.x
- run: npm ci
- run: npm test
test-web:
needs: changes
if: needs.changes.outputs.web == 'true'
runs-on: ubuntu-latest
defaults:
run:
working-directory: packages/web
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20.x
- run: npm ci
- run: npm test
Release automation:
# .github/workflows/release.yml
name: Release
on:
push:
tags:
- 'v*'
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20.x
registry-url: 'https://registry.npmjs.org'
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test
- name: Build
run: npm run build
- name: Generate changelog
id: changelog
uses: metcalfc/changelog-generator@v4
with:
myToken: ${{ secrets.GITHUB_TOKEN }}
- name: Create GitHub Release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ github.ref }}
release_name: Release ${{ github.ref }}
body: ${{ steps.changelog.outputs.changelog }}
draft: false
prerelease: false
- name: Publish to npm
run: npm publish
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
Scheduled workflows:
# .github/workflows/scheduled.yml
name: Scheduled Tasks
on:
schedule:
# Run every day at midnight
- cron: '0 0 * * *'
workflow_dispatch: # Manual trigger
jobs:
update-dependencies:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20.x
- name: Update dependencies
run: |
npm update
npm audit fix
- name: Run tests
run: npm test
- name: Create Pull Request
if: success()
uses: peter-evans/create-pull-request@v5
with:
commit-message: 'chore: update dependencies'
title: 'Automated dependency update'
branch: automated-updates
security-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run npm audit
run: npm audit --audit-level=moderate
- name: Run Snyk scan
uses: snyk/actions/node@master
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
Composite actions:
# .github/actions/setup-nodejs/action.yml
name: 'Setup Node.js with cache'
description: 'Setup Node.js with npm cache'
inputs:
node-version:
description: 'Node.js version'
required: true
default: '20.x'
runs:
using: 'composite'
steps:
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ inputs.node-version }}
cache: 'npm'
- name: Install dependencies
shell: bash
run: npm ci
# Usage in workflow
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup-nodejs
with:
node-version: '20.x'
- run: npm test
Artifacts and outputs:
# .github/workflows/artifacts.yml
name: Build Artifacts
on: [push]
jobs:
build:
runs-on: ubuntu-latest
outputs:
version: ${{ steps.version.outputs.version }}
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20.x
- run: npm ci
- run: npm run build
- name: Get version
id: version
run: echo "version=$(node -p "require('./package.json').version")" >> $GITHUB_OUTPUT
- name: Upload build artifacts
uses: actions/upload-artifact@v3
with:
name: build-${{ steps.version.outputs.version }}
path: dist/
retention-days: 7
deploy:
needs: build
runs-on: ubuntu-latest
steps:
- name: Download artifacts
uses: actions/download-artifact@v3
with:
name: build-${{ needs.build.outputs.version }}
- name: Deploy
run: echo "Deploying version ${{ needs.build.outputs.version }}"
Best Practice Note
Use actions/setup-node@v4 for consistent Node.js setup across workflows. Cache npm dependencies with cache: 'npm' for faster builds. Use npm ci instead of npm install for reproducible builds. Store secrets in GitHub repository settings, never in workflow files. Use matrix strategy to test multiple Node.js versions simultaneously. Use if conditions to prevent unnecessary job execution. This is how we use GitHub Actions for CoreUI Node.js projects—automated testing on every push, matrix builds for version compatibility, and streamlined deployment with environment protection for production-ready CI/CD pipelines.



