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

npm Workspaces no Node.js

Atualizado em: 8 de setembro de 2026

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

Os npm Workspaces no Node.js permitem gerenciar vários pacotes dentro de um único repositório. Uma aplicação pode manter API, workers, biblioteca compartilhada, cliente e ferramentas internas sob o mesmo lockfile, com instalação centralizada e links automáticos entre pacotes locais.

O recurso reduz a necessidade de npm link, facilita executar scripts em um workspace específico ou em todos e mantém versões de dependências coordenadas. Porém, um monorepo sem limites claros pode criar acoplamento, builds lentos e publicação acidental de pacotes privados.

Neste guia, você aprenderá a criar um monorepo com npm Workspaces, adicionar dependências, consumir pacotes locais, executar scripts, configurar TypeScript, CI, publicação e boas práticas de arquitetura.

O que são npm Workspaces?

A documentação de npm Workspaces define workspaces como recursos para gerenciar vários pacotes locais a partir de um pacote raiz. Durante npm install, pacotes declarados são vinculados automaticamente ao node_modules da raiz.

Uma estrutura comum:

meu-projeto/
├── package.json
├── package-lock.json
├── apps/
│   ├── api/
│   │   └── package.json
│   └── worker/
│       └── package.json
└── packages/
    ├── domain/
    │   └── package.json
    └── config/
        └── package.json

Package raiz

{
  "name": "plataforma",
  "private": true,
  "workspaces": [
    "apps/*",
    "packages/*"
  ],
  "scripts": {
    "test": "npm run test --workspaces --if-present",
    "build": "npm run build --workspaces --if-present",
    "lint": "npm run lint --workspaces --if-present"
  }
}

private: true impede publicação acidental do pacote raiz.

Criando um workspace

npm init -w ./packages/domain

O npm cria a pasta, o package.json e atualiza a configuração da raiz quando necessário.

Package de biblioteca

{
  "name": "@empresa/domain",
  "version": "1.0.0",
  "type": "module",
  "exports": {
    ".": "./dist/index.js"
  },
  "types": "./dist/index.d.ts",
  "files": ["dist"],
  "scripts": {
    "build": "tsc -p tsconfig.build.json",
    "test": "node --test"
  }
}

O nome do pacote é usado na resolução local, como seria após publicação.

Adicionando dependência a um workspace

npm install zod -w @empresa/domain

A dependência é gravada no package.json do workspace, enquanto a instalação física pode ser otimizada na raiz.

Dependência entre workspaces

npm install @empresa/domain -w @empresa/api

O npm detecta o pacote local pelo nome e cria o link apropriado. A versão declarada precisa permanecer coerente com o pacote.

Consumindo pacote local

import { Order } from '@empresa/domain';

Evite imports por caminhos relativos que atravessem workspaces:

// Evite
import { Order } from '../../../packages/domain/src/order.js';

O import pelo nome testa o mesmo contrato que consumidores externos usariam.

Um único lockfile

A raiz mantém package-lock.json. Isso facilita instalação reproduzível:

npm ci

Não mantenha lockfiles separados dentro dos workspaces, salvo uma necessidade específica e compreendida.

Executando script em um workspace

npm run test --workspace=@empresa/domain

Forma curta:

npm run test -w @empresa/domain

Executando em todos

npm run test --workspaces

Para ignorar pacotes sem o script:

npm run test --workspaces --if-present

A documentação informa que a ordem segue a lista de workspaces do package.json. Não dependa dessa ordem para builds complexos; use uma ferramenta que compreenda o grafo ou scripts explícitos.

Scripts na raiz

{
  "scripts": {
    "dev:api": "npm run dev -w @empresa/api",
    "dev:worker": "npm run dev -w @empresa/worker",
    "test": "npm run test --workspaces --if-present",
    "format": "prettier . --write"
  }
}

Veja Prettier no Node.js para formatação centralizada.

TypeScript

Uma configuração base pode ficar na raiz:

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "strict": true,
    "declaration": true,
    "sourceMap": true
  }
}

Cada workspace estende:

{
  "extends": "../../tsconfig.base.json",
  "compilerOptions": {
    "outDir": "dist",
    "rootDir": "src"
  },
  "include": ["src"]
}

Project references

TypeScript Project References ajudam em builds incrementais:

{
  "files": [],
  "references": [
    { "path": "packages/domain" },
    { "path": "apps/api" }
  ]
}

Configure composite: true nos projetos referenciados e valide o grafo.

Exports

Não exponha toda a pasta src. Defina uma API pública:

{
  "exports": {
    ".": "./dist/index.js",
    "./errors": "./dist/errors.js"
  }
}

O próximo artigo aprofunda exports e imports.

ESLint centralizado

Uma configuração flat na raiz pode usar padrões por workspace:

{
  files: ['apps/**/*.ts', 'packages/**/*.ts'],
  rules: {
    'no-console': 'error'
  }
}

Consulte ESLint Flat Config no Node.js.

Dependências compartilhadas

Mesmo que o npm faça hoisting, cada workspace deve declarar diretamente tudo que importa. Não dependa de uma biblioteca aparecer no node_modules porque outro pacote a utiliza.

Teste pacotes com instalação empacotada para descobrir dependências fantasmas.

Dependências de desenvolvimento

Ferramentas usadas por todos, como ESLint e Prettier, podem ficar na raiz. Ferramentas exclusivas de um pacote devem ser declaradas nele. Mantenha a regra consistente para reduzir confusão.

Versões internas

Pacotes publicados precisam de versões reais. Pacotes exclusivamente privados podem usar private: true, mas ainda é útil manter versões coerentes para cache e diagnóstico.

Publicação

npm publish -w @empresa/domain

Antes, simule:

npm pack -w @empresa/domain --dry-run

Confirme arquivos, exports, README, licença e versão.

Pacotes privados

{
  "name": "@empresa/api",
  "private": true
}

Aplicações que não devem ir ao registry precisam de private: true.

CI

- run: npm ci
- run: npm run lint
- run: npm run test
- run: npm run build

O artigo CI para Node.js com GitHub Actions mostra cache, services e segurança.

CI por caminhos

Monorepos grandes podem filtrar jobs por mudanças, mas considere dependências transitivas. Alterar domain deve testar API e worker que o consomem.

Docker

Copie primeiro manifests e lockfile para aproveitar cache:

COPY package.json package-lock.json ./
COPY apps/api/package.json apps/api/package.json
COPY packages/domain/package.json packages/domain/package.json
RUN npm ci
COPY . .
RUN npm run build -w @empresa/api

Consulte Docker Multi-stage para Node.js.

Evite acoplamento acidental

Um monorepo facilita imports, mas não elimina fronteiras. Defina:

  • API pública por pacote;
  • direção permitida de dependências;
  • pacotes de domínio sem framework;
  • infraestrutura isolada;
  • regras que bloqueiam imports internos.

Pacote compartilhado gigante

Evite um pacote common que recebe qualquer função reutilizável. Crie bibliotecas coesas, como domain, observability e testing.

Testes

Execute testes unitários por workspace e integração na aplicação consumidora. Um pacote pode passar isoladamente e falhar devido a exports, build ou resolução.

Versionamento independente ou único

Monorepos podem usar:

  • fixed: todos os pacotes compartilham versão;
  • independent: cada pacote evolui separadamente.

A escolha depende de publicação e acoplamento. Changesets ajuda a automatizar ambos.

Comandos úteis

npm ls --workspaces
npm outdated --workspaces
npm update --workspaces
npm run build --workspaces --if-present
npm exec --workspace=@empresa/api -- node -v

Erros comuns

  • Import relativo entre pacotes: quebra encapsulamento.
  • Dependência fantasma: não está declarada.
  • Raiz publicável: risco de publicação acidental.
  • Build sem grafo: consumidor compila antes da biblioteca.
  • Pacote common gigante: acoplamento cresce.
  • CI testa só pacote alterado: consumidores quebram.
  • Exports ausentes: consumidores acessam internals.
  • Lockfiles múltiplos: instalações divergem.

Estrutura recomendada

plataforma/
├── apps/
│   ├── api/
│   └── worker/
├── packages/
│   ├── domain/
│   ├── observability/
│   └── testing/
├── eslint.config.js
├── prettier.config.mjs
├── tsconfig.base.json
├── package-lock.json
└── package.json

Conclusão

Os npm Workspaces no Node.js simplificam monorepos ao centralizar instalação e criar links entre pacotes locais. A mesma resolução usada durante desenvolvimento se aproxima da forma como pacotes publicados são consumidos.

Use nomes de pacote, exports explícitos, dependências declaradas e private: true onde necessário. Com scripts, TypeScript, CI e limites arquiteturais, workspaces oferecem compartilhamento sem transformar o repositório em um bloco fortemente acoplado.

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