NestJS Logo

CI/CD integration

Hint This chapter covers the Nest Devtools integration with the Nest framework. If you are looking for the Devtools application, please visit the Devtools website. CI/CD integration is available on the Enterprise plan.

Local usage is great for exploring your application as you build it, but the real payoff comes when Devtools becomes part of your delivery pipeline. CI/CD integration publishes a snapshot of your application's graph on every build, so you get a running history of how your architecture evolves — and, more importantly, an automatic report on every pull request showing exactly what changed structurally. It's the difference between hoping a refactor didn't break anything and knowing it didn't.

See why teams rely on CI/CD integration to catch architectural drift before it ships:

Publishing graphs#

First, let's wire up the application bootstrap file (main.ts) to use the GraphPublisher class, exported from @nestjs/devtools-integration (see the previous chapter if you haven't installed it yet):


async function bootstrap() {
  const shouldPublishGraph = process.env.PUBLISH_GRAPH === 'true';

  const app = await NestFactory.create(AppModule, {
    snapshot: true,
    preview: shouldPublishGraph,
  });

  if (shouldPublishGraph) {
    await app.init();

    const publishOptions = { ... } // NOTE: this options object will vary depending on the CI/CD provider you're using
    const graphPublisher = new GraphPublisher(app);
    await graphPublisher.publish(publishOptions);

    await app.close();
  } else {
    await app.listen(3000);
  }
}

Here, GraphPublisher pushes your serialized graph to the centralized registry. PUBLISH_GRAPH is a custom environment variable that lets you distinguish a CI/CD publish run from a regular application bootstrap. Setting preview to true makes the app bootstrap in preview mode, meaning constructors and lifecycle hooks on your controllers, enhancers, and providers won't actually execute. This isn't required, but it keeps CI runs fast and simple — no database connection needed, for example.

The shape of publishOptions depends on which CI/CD provider you're using — we'll walk through the most popular ones below. If your provider isn't listed, don't worry: the underlying fields are the same everywhere, so you'll be able to adapt the configuration in minutes.

Once your graph publishes successfully, you'll see output like this in your workflow logs:

Every published graph shows up as a new entry on the project's page:

Reports#

Devtools generates a report for every build, as long as a matching snapshot already exists in the registry. So if you open a pull request against master and a graph for master was already published, Devtools detects the differences and builds a report automatically. Otherwise, there's nothing to compare against — yet.

Find your reports on the project's page (see organizations).

This is where Devtools really earns its keep: catching changes that slip past code review. Say someone quietly changes the scope of a deeply nested provider — easy to miss in a diff, impossible to miss in a report. Remove a guard from an endpoint, and it shows up as an affected change immediately. If that route wasn't covered by integration or e2e tests, you might not have noticed until it was too late — Devtools catches it at review time instead.

The same goes for large codebases: turn a module global, and you'll immediately see how many new edges landed on the graph — usually a strong signal that something needs a second look.

Reports are also a great artifact to link directly in your pull request description. Instead of asking a reviewer to trust that "this only touches the billing module," you can point them at the exact set of nodes and edges that changed — turning an architectural claim into something they can actually verify in seconds.

Build preview#

Every published graph can be replayed — click Preview to see exactly how it looked at that point in time. When a report is available, differences are highlighted directly on the graph:

  • green nodes represent added elements
  • light white nodes represent updated elements
  • red nodes represent deleted elements

Here's what that looks like:

Being able to rewind and compare graphs makes troubleshooting straightforward — no more guessing what changed and when. Set things up so every pull request (or even every commit) gets its own snapshot in the registry, and you'll always have a clear trail to follow. Think of Devtools as version control that actually understands how Nest assembles your application — and can show you the difference, not just describe it.

Integrations: GitHub Actions#

Create a new workflow file in .github/workflows — let's call it publish-graph.yml — and drop in the following:


name: Devtools

on:
  push:
    branches:
      - master
  pull_request:
    branches:
      - '*'

jobs:
  publish:
    if: github.actor!= 'dependabot[bot]'
    name: Publish graph
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
        with:
          node-version: '16'
          cache: 'npm'
      - name: Install dependencies
        run: npm ci
      - name: Setup Environment (PR)
        if: ${{ github.event_name == 'pull_request' }}
        shell: bash
        run: |
          echo "COMMIT_SHA=${{ github.event.pull_request.head.sha }}" >>\${GITHUB_ENV}
      - name: Setup Environment (Push)
        if: ${{ github.event_name == 'push' }}
        shell: bash
        run: |
          echo "COMMIT_SHA=\${GITHUB_SHA}" >> \${GITHUB_ENV}
      - name: Publish
        run: PUBLISH_GRAPH=true npm run start
        env:
          DEVTOOLS_API_KEY: CHANGE_THIS_TO_YOUR_API_KEY
          REPOSITORY_NAME: ${{ github.event.repository.name }}
          BRANCH_NAME: ${{ github.head_ref || github.ref_name }}
          TARGET_SHA: ${{ github.event.pull_request.base.sha }}
Hint For better security, pull DEVTOOLS_API_KEY from GitHub Secrets rather than hardcoding it — read more here.

This workflow runs on every pull request targeting master, as well as on direct pushes to master. Feel free to adapt it to your project's needs — the one non-negotiable is providing the environment variables that GraphPublisher relies on.

Before this workflow will run, though, there's one variable left to fill in: DEVTOOLS_API_KEY. Generate a dedicated API key for your project on the API keys page.

Lastly, head back to main.ts and fill in the publishOptions object we left empty earlier:


const publishOptions = {
  apiKey: process.env.DEVTOOLS_API_KEY,
  repository: process.env.REPOSITORY_NAME,
  owner: process.env.GITHUB_REPOSITORY_OWNER,
  sha: process.env.COMMIT_SHA,
  target: process.env.TARGET_SHA,
  trigger: process.env.GITHUB_BASE_REF ? 'pull' : 'push',
  branch: process.env.BRANCH_NAME,
};

For the smoothest experience, we recommend installing the GitHub application for your project too — just click "Integrate GitHub app" below. Optional, but well worth it.

With the app installed, you'll see the status of your preview/report generation right inside the pull request:

Integrations: Gitlab Pipelines#

Create a .gitlab-ci.yml file in your project root with the following definition:


image: node:16

stages:
  - build

cache:
  key:
    files:
      - package-lock.json
  paths:
    - node_modules/

workflow:
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
      when: always
    - if: $CI_COMMIT_BRANCH == "master" && $CI_PIPELINE_SOURCE == "push"
      when: always
    - when: never

install_dependencies:
  stage: build
  script:
    - npm ci

publish_graph:
  stage: build
  needs:
    - install_dependencies
  script: npm run start
  variables:
    PUBLISH_GRAPH: 'true'
    DEVTOOLS_API_KEY: 'CHANGE_THIS_TO_YOUR_API_KEY'
Hint As with GitHub, we recommend pulling DEVTOOLS_API_KEY from your CI/CD secrets rather than committing it directly.

This pipeline runs on every pull request targeting master, as well as on direct pushes to master. Adjust it to fit your project — just make sure the environment variables GraphPublisher needs are always present.

One variable in this definition still needs a real value: DEVTOOLS_API_KEY. Generate a dedicated API key for your project on the API keys page.

Lastly, head back to main.ts and fill in the publishOptions object we left empty earlier:


const publishOptions = {
  apiKey: process.env.DEVTOOLS_API_KEY,
  repository: process.env.CI_PROJECT_NAME,
  owner: process.env.CI_PROJECT_ROOT_NAMESPACE,
  sha: process.env.CI_COMMIT_SHA,
  target: process.env.CI_MERGE_REQUEST_DIFF_BASE_SHA,
  trigger: process.env.CI_MERGE_REQUEST_DIFF_BASE_SHA ? 'pull' : 'push',
  branch:
    process.env.CI_COMMIT_BRANCH ??
    process.env.CI_MERGE_REQUEST_SOURCE_BRANCH_NAME,
};

Other CI/CD tools#

Not on Github or Gitlab? No problem — the integration doesn't actually depend on either platform. All GraphPublisher needs is a handful of values describing the current build (who triggered it, which commit, which branch), so it works with any provider you throw at it, including Bitbucket Pipelines, CircleCI, and more.

Here's the full publishOptions shape and what each field represents, so you can wire up any pipeline:


const publishOptions = {
  apiKey: process.env.DEVTOOLS_API_KEY, // This is your Nest Devtools API key
  repository: '?', // This is your repository name, for example, "my-api-repository"
  owner: '?', // This is your organization/team name, for example, "nestjs" OR in case of personal projects - your username
  sha: '?', // This represents the "current" commit SHA that triggered the workflow/pipeline
  target: '?', // This represents the "target" commit SHA (e.g., the last commit SHA of the "master" branch)
  // New build will be compared to the "target" build to generate a report
  // NOTE: Some CI/CD tools don't provide you with this information so instead, you can use the "targetBranch" property.
  // NOTE: In this case, the "target" commit SHA will be automatically resolved to the last commit SHA of the "targetBranch" stored in the database.
  // targetBranch: "master",
  trigger: isPr ? 'pull' : 'push', // Depending on whether the pipeline is triggered by a pull request or a regular push commit, you should set "pull" or "push"
  branch: '?', // This is the current branch name, for example, "develop" OR "feat/my-new-feature"
};

Most of these values are already sitting in your CI/CD provider's built-in environment variables — see CircleCI's environment variable reference and Bitbucket's as a starting point.

For your pipeline triggers, we recommend the following setup:

  • push — only for branches that represent a deployment environment, e.g. master, main, staging, or production.
  • pull request — always, or at minimum when the target branch is a deployment environment (see above).

This combination keeps your registry populated with a snapshot for every deployable state of master, while still generating a report on every pull request that targets it — which is exactly the pairing that makes reports useful in the first place.

Support us

Nest is an MIT-licensed open source project. It can grow thanks to the support of these awesome people. If you'd like to join them, please read more here.

Principal Sponsors

SerpApi LogoTrilon LogoMojam Logo

Sponsors / Partners

Become a sponsor