Hcode
Capa NestJS 12: Pacotes ESM, Standard Schema e observabilidade nativa

NestJS 12: Pacotes ESM, Standard Schema e observabilidade nativa

João Rangel

O NestJS 12 foi lançado oficialmente em 27 de agosto de 2026 e é uma das atualizações mais profundas da história do framework — mas, curiosamente, uma das que menos exige reescrita de código de aplicação.

A versão gira em torno de quatro frentes: pacotes ESM, suporte de primeira classe a Standard Schema para validação e serialização, uma CLI reconstruída do zero e observabilidade nativa através do novo pacote @nestjs/observe.

E há um ponto importante logo de saída: aplicações CommonJS continuam funcionando. Migrar o seu próprio código para ESM é totalmente opcional.

Entre os destaques estão:

  • Todos os pacotes do core agora são ESM, com CommonJS continuando a funcionar via require(esm);
  • Standard Schema nos decorators de rota, permitindo Zod, Valibot e ArkType no @Body(), @Query() e @Param();
  • Serialização por schema com o novo StandardSchemaSerializerInterceptor;
  • Observabilidade nativa com o SDK @nestjs/observe;
  • CLI reconstruída, com os novos comandos nest upgrade e nest deploy;
  • Rspack no lugar do webpack, oxlint no lugar do ESLint e Vitest como padrão em projetos ESM;
  • Diagnóstico de conflito de rotas, para identificar rotas sombreadas ou duplicadas;
  • Códigos de erro legíveis por máquina nas exceções HTTP;
  • Logs estruturados por padrão no ConsoleLogger;
  • Node.js v20.19+ ou v22.12+ como requisito mínimo.

Vamos entender o que muda na prática.

Os pacotes do NestJS agora são ESM

A mudança arquitetural mais importante do NestJS 12 é a migração dos pacotes do core de CommonJS para ESM.

Essa discussão já vinha se arrastando no ecossistema Node.js há anos, e o motivo de ela finalmente ter destravado é técnico: o suporte a require(esm) nas versões modernas do Node.js.

Na prática, isso significa que um projeto CommonJS consegue continuar carregando pacotes que são ESM, sem precisar reescrever imports, converter arquivos ou mudar a estratégia de build.

Por isso a recomendação oficial é direta: na maioria dos projetos, nada precisa ser feito.

Ainda assim, vale revisar três pontos que costumam assumir CommonJS de forma implícita:

  • Scripts customizados de bootstrap da aplicação;
  • Ferramentas e configurações de build;
  • Test runners e mocks que dependem do formato dos módulos.

Também é um bom momento para eliminar deep imports — aqueles imports que alcançam caminhos internos dos pacotes do Nest em vez de usar a API pública. Eles sempre foram frágeis, e continuam sendo o tipo de coisa que quebra em uma troca de formato de módulo.

Outra mudança visível está na criação de projetos. O comando:

nest new

agora pergunta se você quer gerar um projeto CommonJS ou ESM. A escolha define também o restante da toolchain, como veremos adiante.

Validação com Standard Schema direto nos decorators

Se você acompanha o ecossistema TypeScript, provavelmente já cruzou com o Standard Schema: uma especificação comum que bibliotecas de validação implementam para poderem ser usadas de forma intercambiável.

O NestJS 12 adota essa especificação de forma nativa.

Os decorators de parâmetro de rota — @Body(), @Query(), @Param() e @RawBody() — passam a aceitar uma nova opção schema, pensada para bibliotecas compatíveis como Zod, Valibot e ArkType:

@Post()
create(@Body({ schema: createUserSchema }) body: CreateUserDto) {
  return this.usersService.create(body);
}

@Get(':id')
findOne(@Param('id', { schema: z.coerce.number().int().positive() }) id: number) {
  return this.usersService.findOne(id);
}

Há um detalhe de implementação que vale entender bem: o decorator apenas anexa o metadado. Ele não valida nada sozinho.

Quem valida é o novo pipe, que precisa ser registrado:

app.useGlobalPipes(new StandardSchemaValidationPipe());

Isso mantém a arquitetura do framework coerente com o que já existia: o decorator descreve a intenção, o pipe executa.

Um ganho importante é que os mesmos schemas alimentam a geração de OpenAPI. Ou seja, você não precisa manter uma definição para validar e outra para documentar.

E, para quem já tem uma base grande escrita no modelo tradicional, a mensagem é tranquilizadora: o fluxo com class-validator continua totalmente suportado, sem qualquer plano de remoção.

Serialização também ganhou uma versão Standard Schema

A mesma ideia foi levada para o caminho de saída da requisição.

O novo StandardSchemaSerializerInterceptor valida e transforma a resposta usando o mesmo ecossistema de schemas:

@UseInterceptors(StandardSchemaSerializerInterceptor)
@SerializeOptions({ schema: userResponseSchema })
@Get(':id')
findOne(@Param('id') id: string) {
  return this.usersService.findOne(id);
}

Isso resolve um problema bastante concreto de APIs em produção: garantir que o objeto devolvido ao cliente contenha exatamente os campos previstos — e não aquilo que acidentalmente veio do banco de dados.

A escolha entre as duas abordagens é por caso de uso:

  • ValidationPipe e ClassSerializerInterceptor para DTOs baseados em classes;
  • Versões Standard Schema quando os seus schemas já existem no projeto.

Observabilidade nativa com @nestjs/observe

Talvez a novidade mais estratégica do NestJS 12 seja o @nestjs/observe, o SDK oficial de observabilidade do framework.

A diferença em relação a um APM genérico está na abordagem. Em vez de instrumentar o servidor HTTP por fora, o SDK se conecta ao próprio ciclo de vida de requisição do Nest, através de uma nova opção da aplicação chamada instrument:

export const { ObserveModule, ObserveInstrument } = createObserveModule();

const app = await NestFactory.create(AppModule, {
  instrument: ObserveInstrument,
});

A consequência prática é que os dados chegam descritos nos termos da sua aplicação: controllers, providers, resolvers e consumidores de fila — e não em termos de rotas HTTP genéricas.

A instrumentação automática cobre:

  • HTTP;
  • GraphQL;
  • gRPC;
  • Transportes de microservices;
  • Consumidores de fila;
  • Execuções de cron.

Sem instrumentação manual de spans e sem precisar rodar um collector.

É um recurso opt-in e novo, ou seja: não há nada para migrar. Tanto o nest new quanto o nest upgrade conseguem fazer a configuração inicial com a flag --observe.

O @nestjs/config agora valida por Standard Schema

O módulo de configuração deixou de ser acoplado ao Joi.

A opção validationSchema passa a aceitar qualquer schema compatível com Standard Schema:

ConfigModule.forRoot({
  validationSchema: z.object({
    NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),
    PORT: z.coerce.number().default(3000),
  }),
});

Quem já usa Joi não precisa reescrever nada, mas precisa observar dois pontos:

  • Atualizar para o Joi v18+, que é a primeira versão a implementar Standard Schema;
  • Mover as configurações específicas da biblioteca para dentro de validationOptions.libraryOptions.

Diagnóstico de conflito de rotas

Esse é o tipo de recurso que só faz sentido para quem já perdeu tempo com o problema.

As rotas do Nest são registradas na ordem de declaração. Em adaptadores sensíveis a ordem, isso significa que uma rota como:

@Get(':id')

pode silenciosamente sombrear uma rota declarada depois:

@Get('me')

O resultado é uma requisição para /users/me chegando no handler de /users/:id com o id valendo a string "me". Nenhum erro, nenhum aviso — só um comportamento errado.

O NestJS 12 traz duas opções opt-in para expor esse cenário:

const app = await NestFactory.create(AppModule, {
  routeConflictPolicy: { duplicate: 'error', shadow: 'warn' },
  routeResolutionStrategy: 'specificity',
});

As duas mantêm o comportamento anterior por padrão, então nada muda a menos que você as configure explicitamente.

Códigos de erro legíveis por máquina

Outra melhoria pequena no tamanho e grande no impacto do dia a dia.

O HttpExceptionOptions passa a aceitar um errorCode, que é serializado no corpo da resposta:

throw new BadRequestException('Password is too weak', { errorCode: 'WEAK_PASSWORD' });

Com isso, o cliente pode ramificar a lógica em cima de um identificador estável, em vez de fazer comparação de string na mensagem de erro — que muda quando alguém corrige uma vírgula ou traduz o texto.

Logs estruturados no ConsoleLogger

O ConsoleLogger agora trata objetos passados depois da mensagem como parâmetros estruturados do mesmo registro, e não como registros separados:

logger.log('User created', { userId: 1, email: '[email protected]' });

No modo JSON, esses dados ficam aninhados sob params, ou podem ser espalhados na raiz com flattenParams.

O comportamento vem ligado por padrão. Para voltar ao formato anterior, basta configurar structuredParams: false.

A CLI do NestJS foi reconstruída

A @nestjs/cli v12 não recebeu apenas comandos novos: ela foi reescrita.

O código-fonte inteiro migrou para ESM, os testes saíram do Jest para o Vitest, foram adicionados testes end-to-end para todos os comandos, e as classes de comando passaram a receber objetos de contexto tipados em vez de entradas e arrays de opções sem tipo.

Novos comandos

  • nest upgrade (alias update) — atualiza um projeto v11 para v12 e aplica as etapas mecânicas da migração;
  • nest deploy — publica a aplicação na nuvem via Mau, instalando o @nestjs/mau no primeiro uso.

Novos padrões de toolchain

  • Rspack passa a ser o bundler padrão para monorepos. As flags --webpack e --webpackPath (e suas equivalentes no nest-cli.json) estão depreciadas em favor de --builder rspack;
  • oxlint substitui o ESLint nos projetos gerados;
  • Vitest é o test runner padrão em projetos ESM; projetos CommonJS continuam com Jest;
  • bun passa a ser um gerenciador de pacotes suportado, ao lado de npm, yarn e pnpm;
  • O schematic decorator passa a gerar decorators no formato preferido Reflector.createDecorator(), e o schematic angular foi removido.

Novas opções

  • nest build e nest start: --rspackPath [path], --emit-declarations (SWC), --no-type-check e --silent;
  • nest build: --parallel [concurrency], para compilar projetos de um monorepo em paralelo junto com --all;
  • nest-cli.json: includeLibraryAssets, para copiar assets de libraries para o build da aplicação.

Novidades espalhadas pelo restante do framework

Além dos destaques principais, o NestJS 12 traz uma série de melhorias menores que resolvem incômodos bem específicos:

  • Formato de erro do ValidationPipe — uma nova opção controla o formato das respostas de erro de validação;
  • Filtro de exceção para gRPC — o GrpcExceptionFilter e exceções específicas por status mapeiam erros para os status corretos do gRPC, em vez de devolver UNKNOWN;
  • Padrões Kafka por expressão regular@MessagePattern() e @EventPattern() aceitam um RegExp no transporte Kafka;
  • Gateways WebSocket com escopo de requisição — gateways passam a suportar providers request-scoped, com o socket injetável pelo token REQUEST;
  • Motivo do disconnect — o handleDisconnect pode receber o motivo da desconexão;
  • Hook pré-requisição em microservices — um novo hook roda antes do handler da mensagem ser invocado;
  • Shutdown gracioso no Express — o adaptador drena as requisições em andamento ao desligar;
  • Mapeamento de erros dos adaptadores HTTP — retrabalhado no core, no Express e no Fastify.

O shutdown gracioso do Express, em particular, é uma daquelas correções que quem roda Kubernetes agradece: durante um rollout, requisições em andamento deixam de ser cortadas no meio.

O que quebra ao subir para o NestJS 12

A equipe optou por concentrar as mudanças incompatíveis fora do código de aplicação. Ainda assim, vale conhecer a lista:

  • Node.js v20.19+ ou v22.12+ — a linha 21.x não é suportada, e o comando de upgrade se recusa a rodar em versões anteriores;
  • Pacotes em ESM — normalmente nada muda, graças ao require(esm); revise bootstrap, bundler e test runner customizados;
  • Hooks de ciclo de vida — agora são invocados por nível de hierarquia dos componentes; revise suposições de ordem entre providers e módulos relacionados;
  • NATS v3 — o pacote nats foi substituído por @nats-io/transport-node; as mensagens passam a ser serializadas como JSON e deserializers customizados recebem a mensagem completa, lida com msg.json();
  • GraphQL subscriptions — o suporte a subscriptions-transport-ws foi removido em favor do graphql-ws; os protocolos são incompatíveis no fio, então os clientes precisam ser atualizados;
  • GraphiQL passa a ser a IDE padrão de GraphQL — troque playground por graphiql;
  • @nestjs/config — validação via Standard Schema, com Joi v18+ e validationOptions.libraryOptions;
  • Assinaturas de pipes — refinadas, com ArgumentMetadata agora genérico; ajuste pipes customizados se o compilador reclamar;
  • ConsoleLogger com params estruturados ligado por padrão;
  • Fluxos com webpack na CLI depreciados, em favor de --builder rspack;
  • Schematic angular removido.

A boa notícia é que a maior parte disso é tratada automaticamente pelo nest upgrade.

Como atualizar para o NestJS 12

O primeiro passo é atualizar a CLI, já que é ela que carrega o comando de upgrade:

npm i -g @nestjs/cli@latest

Depois, a partir da raiz do projeto:

nest upgrade

O comando move todos os pacotes @nestjs/* para a major compatível com a v12 de uma vez só e aplica as partes mecânicas da migração:

  • Opções de webpack no nest-cli.json;
  • Renomeação de playground para graphiql no GraphQL e troca do transporte de subscriptions;
  • Substituição do pacote do NATS;
  • Opções de validação do @nestjs/config;
  • Atualizações de Jest e Joi.

Ao final, ele imprime um relatório com tudo o que alterou e tudo o que ainda precisa de revisão manual.

Antes de aplicar em um projeto real, vale executar:

nest upgrade --dry-run

para ver esse mesmo relatório sem que nenhum arquivo seja tocado.

E um ponto importante: o comando deliberadamente não migra o seu projeto para ESM, Vitest ou oxlint. Esses são os padrões de projetos novos — projetos existentes adotam no próprio ritmo.

Como em qualquer atualização de major, o roteiro seguro continua o mesmo:

  • Rodar toda a suíte de testes automatizados;
  • Validar autenticação, guards, interceptors e pipes customizados;
  • Conferir microservices, filas e jobs agendados;
  • Testar o processo de build e o deploy em um ambiente de staging antes da produção.

Vale a pena atualizar para o NestJS 12?

Para projetos que já estão no NestJS 11, a atualização é bem menos arriscada do que o número da major sugere — justamente porque as mudanças mais radicais acontecem em camadas que a aplicação normalmente não toca.

O que você ganha só por subir de versão:

  • Shutdown gracioso no Express;
  • Mapeamento correto de erros nos adaptadores HTTP;
  • Status adequados nas exceções de gRPC;
  • Logs estruturados no ConsoleLogger.

O que é adoção opcional, e pode entrar depois com calma:

  • Migrar o projeto para ESM;
  • Trocar class-validator por Zod, Valibot ou ArkType;
  • Adotar Vitest, oxlint e Rspack;
  • Ligar o @nestjs/observe.

O principal bloqueio prático não é o framework, e sim o runtime: se o seu ambiente ainda roda uma versão de Node.js anterior à v20.19, essa é a primeira coisa a resolver.

O que o NestJS 12 mostra sobre a direção do framework

Olhando o conjunto, o NestJS 12 tem uma linha bastante clara.

De um lado, o framework está se alinhando ao ecossistema em vez de manter soluções próprias: ESM em vez de CommonJS, Standard Schema em vez de acoplamento a uma biblioteca de validação específica, graphql-ws em vez de um transporte legado.

De outro, está modernizando a toolchain com implementações nativas de alto desempenho — Rspack e oxlint seguem exatamente a mesma tendência que o Turbopack representa no mundo Next.js.

E há uma terceira frente, mais silenciosa: várias novidades desta versão existem para tornar o comportamento do sistema observável. Conflito de rotas que deixa de ser invisível. Código de erro que o cliente consegue interpretar. Log que vira dado estruturado. Observabilidade que fala a linguagem dos seus controllers.

São melhorias menos vistosas do que uma API nova, mas que atacam justamente o tipo de problema que costuma custar caro em produção.

Perguntas frequentes sobre o NestJS 12

Quando o NestJS 12 foi lançado?

O NestJS 12.0.0 foi lançado oficialmente em 27 de agosto de 2026.

O que há de novo no NestJS 12?

As principais novidades são os pacotes em ESM, o suporte de primeira classe a Standard Schema para validação e serialização, a CLI reconstruída com os comandos nest upgrade e nest deploy, e a observabilidade nativa através do SDK @nestjs/observe.

Preciso migrar minha aplicação para ESM no NestJS 12?

Não. Os pacotes do Nest passaram a ser ESM, mas aplicações CommonJS continuam funcionando graças ao require(esm) das versões modernas do Node.js. Migrar o seu próprio código é opcional.

Qual a versão mínima do Node.js para o NestJS 12?

É necessário Node.js v20.19+ ou v22.12+. A linha 21.x não é suportada, e o comando nest upgrade se recusa a rodar em versões anteriores. A recomendação é usar a LTS ativa mais recente.

O class-validator vai ser removido do NestJS?

Não. O suporte a Standard Schema é uma alternativa, não uma substituição. O fluxo baseado em decorators com class-validator continua totalmente suportado, sem plano de remoção.

Como atualizar um projeto do NestJS 11 para o 12?

Atualize a CLI com npm i -g @nestjs/cli@latest e execute nest upgrade na raiz do projeto. Rode antes com --dry-run para ver o relatório do que seria alterado sem tocar nos arquivos.

O NestJS 12 obriga a usar Vitest e oxlint?

Não. Vitest, oxlint e ESM são os padrões apenas para projetos novos gerados pela CLI. O nest upgrade não converte projetos existentes para eles.

O que é o @nestjs/observe?

É o SDK oficial de observabilidade do NestJS. Ele se conecta ao ciclo de vida de requisição do próprio framework pela opção instrument, e instrumenta automaticamente HTTP, GraphQL, gRPC, transportes de microservices, consumidores de fila e execuções de cron — sem instrumentação manual de spans e sem collector.