on
Getting started with CI: a simple GitHub Actions pipeline for Node.js
Continuous integration (CI) helps you catch bugs fast by building and testing every change. GitHub Actions makes CI easy because workflows live in your repository and run on GitHub-hosted runners. This short guide walks through a minimal, practical CI pipeline for a Node.js project: checkout, install, test, run across multiple Node versions, and cache dependencies to speed repeat runs.
What you’ll build
- A workflow file stored in .github/workflows that triggers on push and pull requests.
- A job that:
- checks out the repo,
- sets up Node.js,
- restores or creates a dependency cache,
- installs dependencies with npm,
- runs tests,
- runs across a small matrix of Node versions for broader coverage.
Where workflows live and the basics Workflow YAML files belong in .github/workflows in your repo. GitHub provides a Quickstart and in-depth workflow docs that explain triggers (on:), jobs, steps, and actions; these are the right references when you need specifics. (docs.github.com)
Example workflow (paste into .github/workflows/ci.yml)
name: CI
on:
push:
branches: [ main ]
pull_request:
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [18, 20]
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Use Node.js $
uses: actions/setup-node@v6
with:
node-version: $
cache: 'npm' # let setup-node handle npm cache when possible
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test
Key steps explained
-
actions/checkout@v4 — always start by checking out the repository so the runner can access your code and lockfiles. The checkout action is maintained by GitHub and v4 is the current major release series with recommended security fixes and features. (github.com)
-
actions/setup-node — sets the Node version for the runner. Recent releases of setup-node also integrate caching behavior for npm (when your package.json uses a packageManager or devEngines.packageManager field), which simplifies cache setup. If you want more control, you can still use actions/cache directly. (github.com)
-
npm ci vs npm install — npm ci installs strictly from package-lock.json and is faster and more reproducible for CI. It also works well with cache strategies that key off lockfile changes.
Using a matrix to test multiple Node versions A matrix strategy runs the job in parallel across the Node versions you list. This is a common, compact way to confirm your package works across supported runtimes without duplicating workflow YAML. Matrix usage is a standard practice documented in the workflows guide. (docs.github.com)
Caching dependencies to speed builds Caching the npm cache or other dependency files reduces runtime by avoiding repeated downloads. Use actions/cache (or setup-node’s built-in cache features) to store the appropriate directories and key them to the lockfile hash so a new cache is created whenever your dependencies change. A typical cache key uses hashFiles(‘**/package-lock.json’) and includes the runner OS and Node version to avoid wrong-cache collisions. (docs.github.com)
A few practical notes and tips
-
Cache scope: caching global package caches (like ~/.npm) generally gives better reuse between Node versions than caching node_modules, but caching node_modules can be OK for monorepos or when disk layout makes sense. The setup-node action documents the recommended caching behavior and caveats. (github.com)
-
Security in publishing workflows: avoid blindly enabling caches in workflows that fetch or build code from untrusted sources (for example, workflows that publish packages). A poisoned cache could expose secrets or tokens—setup-node docs call out this caution and provide a way to disable automatic caching in those contexts. (github.com)
-
Cache misses vs hits: when your package-lock.json changes, the key changes and the runner creates a fresh cache. Use restore-keys to provide graceful fallbacks when exact-key hits aren’t found. The actions/cache action provides a cache-hit output you can use to conditionally skip certain steps. (github.com)
-
Keep jobs fast: run only what you need in CI. Lint + unit tests are typical; longer integration tests can be run on nightly or release workflows.
Troubleshooting
-
If workflows don’t trigger, confirm the file is in .github/workflows and the on: triggers match your branch/event. The quickstart docs and example workflows are useful first look-ups. (docs.github.com)
-
If the cache never hits, check that your key expression actually changes only when dependencies change (hashing lockfiles is the usual pattern). Also verify the cache path matches the actual cache directory used by your package manager. (docs.github.com)
Wrap-up This minimal CI pipeline gives you reliable builds and tests on every push and PR, with simple extensions available: add linting steps, test coverage reporting, or artifacts as needed. The GitHub Actions docs and maintained actions (checkout, setup-node, cache) are the right places to check for latest configuration patterns and security notes. (docs.github.com)