Skip to content
Latchkey

CI/CD for a Node/Express API with GitHub Actions

A Node/Express API needs a pipeline that installs deps, lints, runs tests against a real database, and ships on green.

This recipe gives you a working GitHub Actions workflow for an Express API. It runs unit and integration tests against a Postgres service container, then deploys on a push to main. Adapt the deploy step to your host.

What the pipeline does

  • Install dependencies with npm ci
  • Lint with ESLint
  • Run tests against a Postgres service
  • Build (transpile/bundle if needed)
  • Deploy on push to main

The workflow

Two jobs: a test job with a Postgres service container, and a deploy job gated on the default branch.

.github/workflows/ci.yml
name: CI
on: [push, pull_request]
jobs:
  test:
    runs-on: ubuntu-latest
    services:
      postgres:
        image: postgres:16
        env:
          POSTGRES_PASSWORD: postgres
          POSTGRES_DB: app_test
        ports: ["5432:5432"]
        options: >-
          --health-cmd "pg_isready -U postgres"
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
    env:
      DATABASE_URL: postgres://postgres:postgres@localhost:5432/app_test
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: npm
      - run: npm ci
      - run: npm run lint
      - run: npm test
  deploy:
    needs: test
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: npm
      - run: npm ci --omit=dev
      - run: npm run deploy

Testing against a database

The postgres service runs in a sidecar container reachable at localhost:5432 from the job. The health check ensures the database is accepting connections before your tests start, so point your test config at DATABASE_URL and run migrations in a beforeAll or a dedicated step. Most registry pulls and network calls here are transient; managed runners that auto-retry these flakes keep the job green without a manual re-run, and Latchkey is about 69% cheaper than GitHub-hosted runners.

Deploying

The deploy job runs only on main. Swap npm run deploy for your target: a platform CLI (Fly, Render, Railway), an SSH rsync to a VPS, or a container push to ECS. Store credentials in repository secrets and reference them as environment variables in the deploy step.

Key takeaways

  • Run integration tests against a Postgres service container, not a mock.
  • Gate the deploy job on github.ref == refs/heads/main and needs: test.
  • Cache npm via setup-node to cut install time on every run.

Related guides

Run this faster and cheaper on Latchkey managed runners. Start free →