Home > Blog > Desenvolvimento Web
Desenvolvimento Web
JavaScript
Programação

CI para Node.js com GitHub Actions

Atualizado em: 8 de setembro de 2026

Estação de trabalho usada no desenvolvimento de aplicações Node.js

CI para Node.js com GitHub Actions automatiza instalação de dependências, lint, typecheck, testes, cobertura e build a cada mudança no repositório. Uma pipeline bem desenhada impede que erros simples cheguem à branch principal, oferece feedback rápido e produz artefatos reproduzíveis para etapas posteriores de publicação ou deploy.

O objetivo da integração contínua não é apenas executar npm test. Ela precisa fixar a versão do runtime, respeitar o lockfile, usar permissões mínimas, testar versões suportadas, armazenar relatórios e evitar que código de pull requests não confiáveis acesse segredos.

Neste guia, você aprenderá a criar um workflow Node.js, configurar matrix, cache, npm ci, jobs separados, PostgreSQL e Redis como serviços, cobertura, artifacts, concurrency e práticas de segurança.

O que é integração contínua?

Integração contínua significa validar alterações frequentemente e de forma automatizada. O workflow deve responder rapidamente se o código:

  • instala com o lockfile;
  • respeita padrões de lint;
  • compila ou passa no typecheck;
  • executa testes;
  • mantém cobertura mínima;
  • gera o build esperado;
  • não contém vulnerabilidades ou segredos conhecidos.

A documentação oficial de build e testes Node.js no GitHub Actions apresenta setup-node, matrix, cache, dependências e artifacts.

Primeiro workflow

Crie .github/workflows/ci.yml:

name: CI

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

permissions:
  contents: read

jobs:
  test:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v6

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: npm

      - name: Install dependencies
        run: npm ci

      - name: Lint
        run: npm run lint

      - name: Typecheck
        run: npm run typecheck --if-present

      - name: Test
        run: npm test

      - name: Build
        run: npm run build --if-present

Por que usar npm ci?

npm ci instala exatamente o conteúdo do lockfile, remove node_modules existente e falha quando package.json e lockfile estão inconsistentes.

npm ci

Isso torna o CI mais previsível do que npm install. Para proteção de dependências, consulte Segurança de Dependências no Node.js.

Fixe a versão do Node.js

Não dependa do runtime padrão do runner. Use actions/setup-node e mantenha a versão alinhada ao projeto:

- uses: actions/setup-node@v4
  with:
    node-version-file: '.nvmrc'
    cache: npm

Também é possível ler do package.json quando a ação e o formato suportarem a configuração usada.

Matrix de versões

Bibliotecas devem testar todas as versões suportadas:

strategy:
  fail-fast: false
  matrix:
    node-version: [20, 22, 24]

steps:
  - uses: actions/checkout@v6
  - uses: actions/setup-node@v4
    with:
      node-version: ${{ matrix.node-version }}
      cache: npm
  - run: npm ci
  - run: npm test

Aplicações internas geralmente precisam validar principalmente a versão usada em produção, com um job adicional para a próxima versão antes de migrações.

Cache correto

O cache de setup-node armazena dados do gerenciador, não o diretório node_modules. Isso preserva a instalação limpa.

with:
  node-version: 22
  cache: npm
  cache-dependency-path: package-lock.json

Em monorepos:

cache-dependency-path: |
  package-lock.json
  packages/*/package-lock.json

Jobs separados

Separar lint, testes e build oferece feedback paralelo:

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: npm
      - run: npm ci
      - run: npm run lint

  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: npm
      - run: npm ci
      - run: npm test

  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: npm
      - run: npm ci
      - run: npm run build

A desvantagem é repetir instalação. Para projetos pequenos, um único job pode ser mais rápido e barato.

Scripts no package.json

{
  "scripts": {
    "lint": "eslint .",
    "typecheck": "tsc --noEmit",
    "test": "node --test",
    "test:coverage": "node --test --experimental-test-coverage",
    "build": "tsc -p tsconfig.build.json"
  }
}

O CI deve executar comandos que também funcionam localmente.

Testes com PostgreSQL

jobs:
  integration:
    runs-on: ubuntu-latest

    services:
      postgres:
        image: postgres:17
        env:
          POSTGRES_USER: test
          POSTGRES_PASSWORD: test
          POSTGRES_DB: app_test
        ports:
          - 5432:5432
        options: >-
          --health-cmd="pg_isready -U test"
          --health-interval=10s
          --health-timeout=5s
          --health-retries=5

    env:
      DATABASE_URL: postgresql://test:test@localhost:5432/app_test

    steps:
      - uses: actions/checkout@v6
      - uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: npm
      - run: npm ci
      - run: npm run migrate
      - run: npm run test:integration

Para ambientes mais flexíveis, consulte Testcontainers no Node.js.

Redis como serviço

services:
  redis:
    image: redis:8
    ports:
      - 6379:6379
    options: >-
      --health-cmd="redis-cli ping"
      --health-interval=10s
      --health-timeout=5s
      --health-retries=5

Migrations

Execute migrations antes dos testes de integração. Use banco descartável e confirme rollback ou recriação. Veja Migrações de Banco no Node.js.

Cobertura

- name: Tests with coverage
  run: npm run test:coverage

- name: Upload coverage
  uses: actions/upload-artifact@v4
  with:
    name: coverage
    path: coverage/
    if-no-files-found: error
    retention-days: 7

Defina thresholds responsáveis. Cobertura alta não garante qualidade. Consulte Cobertura de Testes no Node.js.

Artifacts

Armazene:

  • relatórios de cobertura;
  • resultados JUnit;
  • screenshots de testes;
  • logs de falha;
  • build produzido;
  • benchmarks.
- uses: actions/upload-artifact@v4
  if: always()
  with:
    name: test-results
    path: test-results/
    retention-days: 7

if: always() preserva diagnóstico mesmo quando testes falham.

Concurrency

Cancele execuções antigas da mesma branch:

concurrency:
  group: ci-${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

Não cancele jobs de publicação ou migrations sem avaliar efeitos.

Timeout

jobs:
  test:
    timeout-minutes: 15

Um timeout impede runners presos por testes que nunca terminam.

Permissões mínimas

permissions:
  contents: read

Conceda packages: write, id-token: write ou outras permissões apenas ao job que precisa.

Segredos e pull requests

Pull requests de forks não devem receber segredos. Evite executar código não confiável com pull_request_target e checkout do conteúdo do fork, pois isso pode expor o token do workflow.

Para publicar ou acessar registro privado, use jobs protegidos, ambientes e eventos apropriados.

Pin de actions

Tags como @v4 são convenientes, mas organizações com exigência elevada podem fixar actions por SHA imutável e usar ferramentas de atualização automática. Avalie também a procedência de actions de terceiros.

Script injection

Não injete diretamente títulos ou nomes controlados pelo usuário em run:

- run: echo "${{ github.event.pull_request.title }}"

Prefira variável de ambiente:

- name: Print title
  env:
    PR_TITLE: ${{ github.event.pull_request.title }}
  run: printf '%s\n' "$PR_TITLE"

Auditoria de dependências

- name: Audit production dependencies
  run: npm audit --omit=dev --audit-level=high

O comando pode gerar ruído. Defina política de triagem, exceções temporárias e prazo de correção.

Lint e formatação

- run: npm run lint
- run: npm run format:check

Os próximos artigos sobre ESLint e Prettier aprofundam essas etapas.

Testes de contrato

Em microserviços, publique ou verifique contratos somente após testes unitários:

- run: npm run test:contract

Veja Contract Testing com Pact.

Benchmark no CI

Benchmarks em runners compartilhados possuem variação. Use-os para detectar regressões grandes, não diferenças mínimas. Salve resultados e compare tendências.

Consulte Autocannon no Node.js.

Monorepo

Use filtros por caminhos para evitar jobs desnecessários:

on:
  pull_request:
    paths:
      - 'packages/api/**'
      - 'package-lock.json'
      - '.github/workflows/ci.yml'

Uma matrix pode executar por pacote, mas limite a expansão para controlar custo.

Reusable workflows

Organizações com vários repositórios podem criar workflow reutilizável com versões, instalação e verificações padrão. Mantenha inputs explícitos e não esconda permissões elevadas.

Branch protection

Configure a branch principal para exigir:

  • checks aprovados;
  • reviews;
  • branch atualizada quando necessário;
  • conversas resolvidas;
  • assinatura ou regras adicionais conforme política.

Deploy separado

CI valida código. CD publica ou implanta. Mantenha deploy em job ou workflow dependente de checks, com ambientes, aprovação e rollback. Consulte Deploy com GitHub Actions.

Erros comuns

  • Usar npm install: lockfile pode mudar.
  • Não fixar Node: runner muda comportamento.
  • Cachear node_modules: artefatos inconsistentes.
  • Segredo em PR: risco de exfiltração.
  • Permissões amplas: token pode causar dano.
  • Sem timeout: job fica preso.
  • Matrix excessiva: custo e tempo aumentam.
  • Deploy junto sem proteção: falha de validação pode publicar.

Workflow recomendado

name: Node CI

on:
  push:
    branches: [main]
  pull_request:

permissions:
  contents: read

concurrency:
  group: node-ci-${{ github.ref }}
  cancel-in-progress: true

jobs:
  verify:
    runs-on: ubuntu-latest
    timeout-minutes: 15

    steps:
      - uses: actions/checkout@v6

      - uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: npm

      - run: npm ci
      - run: npm run lint
      - run: npm run typecheck --if-present
      - run: npm run test:coverage
      - run: npm run build --if-present

      - uses: actions/upload-artifact@v4
        if: always()
        with:
          name: coverage
          path: coverage/
          retention-days: 7

Conclusão

CI para Node.js com GitHub Actions cria uma barreira automática entre uma alteração e a branch principal. Com runtime explícito, npm ci, cache do gerenciador, lint, typecheck, testes, cobertura e build, o repositório ganha feedback rápido e reproduzível.

Complete a pipeline com permissões mínimas, proteção de segredos, timeouts, concurrency e artifacts. Quando CI e deploy são separados e os checks são obrigatórios, GitHub Actions deixa de ser apenas um executor de comandos e se torna parte da segurança e qualidade do projeto Node.js.

10 melhores cursos de programação em 2026

Descubra os melhores cursos de programação. Aprenda a escolher o curso ideal para iniciar ou avançar na carreira de desenvolvedor

POSTS RELACIONADOS

Ver todos

Seta para a direita