Node.js roda TypeScript sem build step: o que o type-stripping nativo libera e o que ainda quebra no backend
Desde 2024 o Node.js consegue executar arquivos .ts removendo os tipos em tempo real, sem ts-node, tsx ou swc. Mas enums, namespaces com código e decorators continuam fora do alcance.

Desde 2024 o Node.js↳Node.js45 conteúdosComo criar aplicações console em Node.jsDev (Back & Front) · fev 2025Entendendo o ciclo de publicação do Node.jsDev (Back & Front) · fev 2020Nodejs por baixo dos panosDev (Back & Front) · jul 2019Ver tudo em Dev (Back & Front) → consegue executar arquivos .ts removendo os tipos em tempo real, sem ts-node, tsx ou swc. Mas enums, namespaces com código e decorators continuam fora do alcance.
A documentação oficial do Node.js, na página Modules: TypeScript, descreve um recurso que já mudou a esteira de quem mantém serviços backend em produção: o type-stripping nativo. Ele não é novidade de hoje. A flag experimental --experimental-transform-types apareceu na v22.7.0, o recurso passou a vir ligado por padrão na v23.6.0 (e na v22.18.0, no branch LTS), deixou de emitir aviso experimental na v24.3.0/v22.18.0, virou estável na v25.2.0/v24.12.0 e, na v26.0.0, a flag experimental foi removida de vez.
O que o Node faz, de fato, com um arquivo .ts
Type-stripping não é compilação. O Node lê o arquivo .ts, localiza a sintaxe de tipo (anotações, interfaces, as Tipo) e substitui cada trecho por espaços em branco, preservando as posições de linha e coluna. Não há checagem de tipo, não há geração de código novo e, por consequência, não há necessidade de source maps: o stack trace de um erro em produção já aponta para a linha certa do .ts original, porque o espaçamento não mudou.
Isso explica a regra central do recurso: só funciona com sintaxe erasável - aquela que pode virar espaço em branco sem alterar o comportamento do programa. Tipagem pura cumpre esse requisito. Qualquer construção que precise virar código JavaScript novo, não.
O caminho mais curto: tirar tsx ou ts-node do script de start
Para um serviço HTTP comum, sem enum nem decorator, o caminho fica assim:
// server.ts
import type { IncomingMessage, ServerResponse } from 'node:http';
import { createServer } from 'node:http';
function handler(req: IncomingMessage, res: ServerResponse): void {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ ok: true }));
}
createServer(handler).listen(3000);Com "type": "module" no package.json, rodar é node server.ts, sem instalar nada a mais. O detalhe que costuma pegar quem vem de ts-node é a extensão obrigatória: import './util.ts' funciona, import './util' não. A mesma regra vale para require('./file.ts') em projetos CommonJS (arquivos .cts).
A documentação recomenda TypeScript 5.8 ou mais recente e um tsconfig.json com erasableSyntaxOnly: true, verbatimModuleSyntax: true, module: nodenext e rewriteRelativeImportExtensions: true - mas isso é só para o editor e para o tsc usado como checador de tipos em paralelo, nunca em tempo de execução. O Node ignora o tsconfig.json por completo.
O que ainda obriga a manter um transpiler no CI
A própria documentação lista as construções que geram ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX, porque todas exigem gerar JavaScript novo, não apenas apagar anotação:
- Enums (
enum Cor { Vermelho, Verde }) - viram objeto em runtime, não é tipo puro. - Namespaces com código de runtime - um
namespace A { export let x = 1 }quebra; umnamespace TypeOnly { export type A = string }funciona, porque não sobra nada em tempo de execução. - Parameter properties (
constructor(private nome: string) {}) - o açúcar sintático que cria o campo da classe a partir do parâmetro do construtor. - Import aliases no estilo
import A = require('mod'). - Decorators - ainda proposta Stage 3 no TC39; o Node não faz polyfill e trata como erro de parser até virarem JavaScript padrão.
Quem usa NestJS, TypeORM com decorators ou qualquer framework apoiado em @Injectable()/@Entity() não tem como rodar direto com type-stripping hoje. O mesmo vale para bases de código legadas cheias de enum - trocar por as const com union type resolve, mas é refatoração, não configuração.
Importação de tipo exige a palavra type, sem exceção
Como o stripping é cego ao significado - só olha sintaxe -, a diferença entre importar um tipo e importar um valor precisa estar explícita no código:
import type { Usuario } from './modelos.ts'; // ok: vira espaço em branco
import { Usuario } from './modelos.ts'; // erro em runtime se Usuario for só tipoSem o type explícito, o Node trata o import como valor e o módulo quebra ao tentar resolver algo que não existe no arquivo compilado. A opção verbatimModuleSyntax do tsconfig.json ajuda o tsc a cobrar essa disciplina no editor, antes de chegar ao runtime.
paths do tsconfig não existe para o Node
Quem depende de alias de caminho (@app/* apontando para src/app/*) via paths do tsconfig.json perde esse recurso: o Node não lê o arquivo e gera erro ao encontrar o import. A alternativa oficial são os subpath imports do package.json (campo imports), com a limitação de que a chave precisa começar com # - um mecanismo mais limitado e menos familiar do que paths, mas nativo e sem transpiler.
Pacotes de terceiros continuam fora
Para desencorajar bibliotecas publicadas em TypeScript puro, o Node se recusa a aplicar type-stripping em arquivos .ts dentro de qualquer pasta sob node_modules. Isso significa que o recurso resolve o código da sua aplicação, não a cadeia de dependências - quem publica pacote ainda precisa compilar para JavaScript antes de subir ao npm, como sempre foi.
Onde isso efetivamente substitui ts-node, tsx e swc-node
| Cenário | Precisa de transpiler? |
|---|---|
| Script CLI simples, tipos e interfaces apenas | Não |
Serviço HTTP sem enum/decorator, import type disciplinado | Não |
Projeto com NestJS, TypeORM ou qualquer @Decorator() | Sim |
Base de código com enum espalhado | Sim (ou refatorar para as const) |
Alias de import via paths do tsconfig | Sim, ou migrar para subpath imports com # |
Arquivo .tsx (JSX + TypeScript) | Sim - .tsx não é suportado pelo type-stripping |
Em resumo: para backend puro - sem JSX, sem decorator, sem enum -, dá para tirar tsx ou ts-node do script de start e do Dockerfile, reduzindo uma dependência de build inteira. Para o resto, inclusive qualquer front-end em .tsx, o transpiler continua no caminho, e a estratégia mais sã costuma ser migrar primeiro as partes erasáveis e manter o restante no fluxo de build que já existe.
Vale lembrar que o suporte cobre --eval e entrada via STDIN, mas não cobre REPL, --check nem node inspect - detalhe que pega quem tenta depurar um trecho de TypeScript direto no terminal interativo e recebe erro de sintaxe sem entender por quê.
Fonte: Documentação oficial do Node.js, Modules: TypeScript.
Fonte: Documentação oficial do Node.js — TypeScript (Modules: TypeScript)
Este artigo foi escrito por Bisneto Braga, colunista de back-end. Conteúdo produzido por agente de IA da redação iMasters, sob revisão editorial humana. Saiba como produzimos no expediente.
PostgreSQL 18 completa um ano: veja como testar o I/O assíncrono antes de ir para produção
Lançado em setembro de 2025, o Postgres 18 trouxe um subsistema de I/O assíncrono que ataca direto o gargalo de disco em sequential scans, bitmap heap scans e vacuum. Veja como o io_method funciona e como validar cada modo antes de trocar em produção.















