I have set up GitHub Actions for a Node.js project for the first time. I’ll use this as exercise as an example on how bogged down in details one can get and how a seemingly simple task can raise a myriad of questions, ultimately leaving one unable to see the forest for the trees.

Our first stop is GitHub Help on Using Node.js with GitHub Actions. Right at the start, you’ll see the following template1:

name: Node.js CI

on: [push]

jobs:
  build:

    runs-on: ubuntu-latest

    strategy:
      matrix:
        node-version: [8.x, 10.x, 12.x]

    steps:
    - uses: actions/checkout@v2
    - name: Use Node.js ${{ matrix.node-version }}
      uses: actions/setup-node@v1
      with:
        node-version: ${{ matrix.node-version }}
    - run: npm install
    - run: npm run build --if-present
    - run: npm test
      env:
        CI: true

Some might take this template and be done with it. However, I wouldn’t be writing this blog post if my journey ended here. For my part, this template raises a few questions—mostly why? Why does only one step have a name? Why npm install as opposed to npm ci? Why do we need to specify the CI environment variable ourselves? On a smaller scale, I’m also wondering whether Node.js’s version should be prefixed with a “v” and why there’s a blank line after build:.

Digging deeper, right above the template, the documentation links to the Node.js workflow template. There, you’ll find this slightly different template2:

# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions

name: Node.js CI

on:
  push:
    branches: [ master ]
  pull_request:
    branches: [ master ]

jobs:
  build:

    runs-on: ubuntu-latest

    strategy:
      matrix:
        node-version: [10.x, 12.x]

    steps:
    - uses: actions/checkout@v2
    - name: Use Node.js ${{ matrix.node-version }}
      uses: actions/setup-node@v1
      with:
        node-version: ${{ matrix.node-version }}
    - run: npm ci
    - run: npm run build --if-present
    - run: npm test
      env:
        CI: true

Why does this template define different on events? Why does this template actually use npm ci over npm install?

We haven’t even peeked at GitHub Action’s actual reference documentation and are already curious about a number of things.

Typically, in such cases, I turn to open-source projects where their maintainers care more about such things than usual. GitHub Actions aren’t that widespread yet, however, I did find both AVA and Refined GitHub using GitHub Actions. Looking at their workflow configuration, there’s definitely the one or other line to be inspired by.

Jumping ahead, my template might look like this:

name: Node.js CI

on: push

jobs:
  Test:
    runs-on: ubuntu-latest

    strategy:
      matrix:
        node-version: [10.x, 12.x]

    steps:
    - uses: actions/checkout@v2
    - uses: actions/setup-node@v1
      with:
        node-version: ${{ matrix.node-version }}
    - run: npm ci
    - run: npm test
  • on

GitHub’s reference documentation lists a few more examples such as on: push and on: [push, pull_request]. I do wonder why the first template uses [push] while the second template considers both pushes and pull requests but only on the master branch. To keep it simple, push (without brackets) seems like a good choice.

  • jobs.build to jobs.Test

While build is simply the job’s ID, this identifier actually seems to be used as the job’s name (in absence of an explicit name) and will therefore be displayed on GitHub. In that case, let’s use a proper title.

Furthermore, Test seems more appropriate than Build because we are actually running npm test.

  • Job names

We could give every job a custom name but this might be unnecessarily elaborate. Therefore, let’s just remove the name from that single step.

  • npm run build

While --if-present is nice, we don’t need this step if we don’t actually have a build script. Furthermore, even if we’d have one, we might just as well specify a prepare script so it’s automatically ran on npm install.

  • CI environment variable

Looking at GitHub Actions’s default environment variables, CI is always set to true. Therefore, there seems to be no good reason for defining it again.


There’s still room for improvement (nothing is so perfect that it can’t be complained about) but it’s an improvement compared to the original starter template, especially when asking the question: How much yak shaving could we eliminate by lowering the number of questions being raised?