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-presentPor 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 ciIsso 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: npmTambé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 testAplicaçõ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.jsonEm monorepos:
cache-dependency-path: |
package-lock.json
packages/*/package-lock.jsonJobs 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 buildA 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:integrationPara 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=5Migrations
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: 7Defina 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: 7if: 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: trueNão cancele jobs de publicação ou migrations sem avaliar efeitos.
Timeout
jobs:
test:
timeout-minutes: 15Um timeout impede runners presos por testes que nunca terminam.
Permissões mínimas
permissions:
contents: readConceda 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=highO 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:checkOs 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:contractVeja 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: 7Conclusã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.




