Skip to content

Instantly share code, notes, and snippets.

@bradtraversy
Last active October 5, 2026 22:07
Show Gist options
  • Select an option

  • Save bradtraversy/b6b1e16322cf60d8f41fbda71b1c607e to your computer and use it in GitHub Desktop.

Select an option

Save bradtraversy/b6b1e16322cf60d8f41fbda71b1c607e to your computer and use it in GitHub Desktop.
CI/CD Crash Course: Express, GitHub Actions, Render deployments, and pull request previews

CI/CD Crash Course: Commands and Code

Clone the starter app

The starter includes the Express/TypeScript app, a health endpoint test, TypeScript configuration, and the basic npm scripts.

Run these commands in Bash or Zsh (Git Bash on Windows).

git clone https://github.com/bradtraversy/cicd-lab-starter.git cicd-lab
cd cicd-lab
code .

From inside the newly cloned cicd-lab folder, remove the starter's Git history and remote connection, then initialize your own repository on main. Your app files are preserved.

rm -rf .git
git init -b main

The starter already declares Node.js 24 in engines and includes matching Node types. Check your local version before installing dependencies:

node --version

If the output does not start with v24., install or switch to Node.js 24 before continuing. The engines field does not switch your local Node version.

Install dependencies from the lockfile:

npm ci

Run the app

npm run dev

Visit http://localhost:3000 and http://localhost:3000/api/health. The health endpoint should return HTTP 200 with this JSON:

{"status":"ok"}

Run the existing checks

The starter includes these scripts in package.json:

"scripts": {
  "dev": "tsx watch src/server.ts",
  "typecheck": "tsc --noEmit",
  "test": "node --import tsx --test test/app.test.ts",
  "build": "tsc",
  "start": "node dist/server.js"
}

The test in test/app.test.ts starts its own server and checks that /api/health returns HTTP 200 and { "status": "ok" }.

Run the checks in a second terminal. The build compiles JavaScript into dist.

npm test
npm run typecheck
npm run build

Add one verification command

Add this entry inside the scripts object in package.json, including the necessary comma between entries:

"verify": "npm run typecheck && npm test && npm run build"

Run all checks with one command. If a check fails, && stops the remaining commands.

npm run verify

Commit the starting app and connect GitHub

The starter's .gitignore excludes dependencies, build output, and environment secrets. Include package-lock.json in your commit.

git status --short
git add .
git commit -m "feat: add starter app with local verification"

Create an empty cicd-lab repository on GitHub without a README, license, or gitignore. Use a public repository to use rulesets on GitHub Free.

git remote add origin https://github.com/YOUR_USERNAME/cicd-lab.git
git push -u origin main

Deploy the small app on Render

Connect your GitHub account to Render and create a Node Web Service from your own repository, branch main, with a blank root directory and the Free instance for this demo.

Build Command:

npm ci && npm run build

Start Command:

npm start
Health Check Path: /api/health
Auto-Deploy: On Commit

Wait for a successful deployment, then confirm the public home page and /api/health response.

Add GitHub Actions

git switch -c feature/github-actions-ci
mkdir -p .github/workflows

Create .github/workflows/ci.yml; the job runs for pull requests into main and pushes to main.

name: CI

on:
  push:
    branches:
      - main
  pull_request:
    branches:
      - main

permissions:
  contents: read

jobs:
  verify:
    name: Verify
    runs-on: ubuntu-latest
    timeout-minutes: 10

    steps:
      - name: Check out repository
        uses: actions/checkout@v7

      - name: Set up Node.js
        uses: actions/setup-node@v7
        with:
          node-version: 24
          cache: npm

      - name: Install dependencies
        run: npm ci

      - name: Verify application
        run: npm run verify

Stop the development server, then reproduce the CI installation and checks locally.

npm ci
npm run verify

Review, commit, and publish this branch.

git add .
git commit -m "ci: add GitHub Actions verification workflow"
git push -u origin feature/github-actions-ci

Open a pull request into main on GitHub.

Wait for Verify to pass, inspect its Actions logs, then merge; merging triggers another run for main.

Merge the pull request using Create a merge commit, click Delete branch on GitHub if it is still available, then run:

git switch main
git pull --ff-only
git branch -d feature/github-actions-ci

Deploy only after CI passes

After the GitHub Actions workflow is merged, wait for the Verify run on main to pass. In the Render web service settings, change Auto-Deploy from On Commit to After CI Checks Pass. Future pushes to main will deploy only after their CI checks pass.

Require Verify before merging

In GitHub Settings > Rules > Rulesets, create Protect main with these settings; the workflow reports results and the ruleset enforces them.

Enforcement: Active
Target: Default branch (main)
Require a pull request before merging: Enabled
Required approvals: 0 for this solo demo
Require status checks to pass: Enabled
Required check: Verify (GitHub Actions)
Bypass list: Empty

Send a normal change through the pipeline

git switch -c feature/update-homepage

Add to the home page HTML in src/app.ts.

<p>Verified by GitHub Actions. Deployed on Render.</p>

Run the local checks.

npm run verify

Review, commit, and publish this branch.

git add .
git commit -m "feat: update homepage deployment message"
git push -u origin feature/update-homepage

Open a pull request into main on GitHub.

Wait for Verify before merging.

Merge the pull request using Create a merge commit, click Delete branch on GitHub if it is still available, then run:

git switch main
git pull --ff-only
git branch -d feature/update-homepage

Wait for the main check and Render deployment, then confirm the new message on the public page.

Demonstrate a failing test

git switch -c feature/version-endpoint

Append to test/app.test.ts before creating the route.

test('GET /api/version returns the current version', async () => {
  const response = await fetch(`${baseUrl}/api/version`);

  assert.equal(response.status, 200);
  assert.deepEqual(await response.json(), {
    version: process.env.APP_VERSION ?? 'development',
  });
});

This deliberately fails with HTTP 404 instead of 200.

npm test

Review, commit, and publish this branch.

git add .
git commit -m "test: demonstrate missing version endpoint"
git push -u origin feature/version-endpoint

Open a pull request into main on GitHub.

Inspect the failed Verify job and blocked merge; leave this pull request open.

Fix the failure by adding this route before export default app in src/app.ts.

app.get('/api/version', (_request, response) => {
  response.json({
    version: process.env.APP_VERSION ?? 'development',
  });
});

Confirm both tests and the build pass.

npm run verify
git add .
git commit -m "feat: add version endpoint"
git push

The existing pull request updates automatically; wait for its new Verify run to pass.

Merge the pull request using Create a merge commit, click Delete branch on GitHub if it is still available, then run:

git switch main
git pull --ff-only
git branch -d feature/version-endpoint

After deployment, open /api/version; with no APP_VERSION set, expect:

{"version":"development"}

Preview the small app

A preview deployment is a temporary copy of your app built from a pull request, with its own URL, so you can check the change in a browser before merging. Production keeps serving main, and Render deletes the preview when the pull request is merged or closed.

In the Render Dashboard, open your web service's Previews tab, set Pull Request Previews to Automatic, and save. Each preview is a separate running instance, so check the pricing shown. To preview only selected pull requests, choose Manual and put [render preview] in the pull request title. See Render service previews.

git switch -c feature/preview-demo

Add to the home page HTML in src/app.ts.

<p>Preview deployment</p>

Verify the change.

npm run verify

Review, commit, and publish this branch.

git add .
git commit -m "feat: add preview deployment message"
git push -u origin feature/preview-demo

Open a pull request into main on GitHub and leave it unmerged. With Automatic enabled, use a normal title:

Add preview deployment message

If you selected Manual instead, use this title to request a preview:

[render preview] Add preview deployment message

Opening the pull request triggers the preview; you do not need to merge it first. Wait for Verify and the preview deployment. Open the preview from the pull request's View deployment link or the service's Previews tab in Render.

Compare the two URLs while the pull request is still open: the preview should display the new message, and production should not. Only merge after reviewing the preview.

Merge the pull request using Create a merge commit, click Delete branch on GitHub if it is still available, then run:

git switch main
git pull --ff-only
git branch -d feature/preview-demo

Confirm the production message after deployment and that Render removes the temporary preview.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment