9 min

Como configurar uma API Node.js com TypeScript em 2026

Gregory Serrao
Monte uma API Node.js com TypeScript sem ts-node, sem nodemon e sem build — o Node roda TypeScript nativamente. Tudo executado e verificado.

Se você aprendeu a montar API em Node com TypeScript há um ou dois anos, metade do que você configurou não é mais necessária. O ts-node, o nodemon, o passo de build antes de rodar — o Node faz tudo isso sozinho agora.

Este guia monta uma API do zero com o que existe hoje, e explica o que você pode remover do seu setup atual. Todos os comandos e saídas abaixo foram executados num container limpo em 15/08/2026.

Versões usadas: Node v24.19.0 (LTS Krypton) · npm 11.17.0 · Express 5.2.1 · TypeScript 7.0.2 · @types/node 26.2.0

O que mudou desde 2024

Três coisas, e todas apagam configuração:

antes agora
ts-node ou tsx para rodar .ts node arquivo.ts direto
nodemon para recarregar node --watch nativo
express-async-errors para erro em rota async Express 5 trata sozinho

Nenhuma dessas dependências precisa existir num projeto novo.


O Node roda TypeScript sozinho

Crie um arquivo .ts e rode:

$ cat t.ts
type Pedido = { id: number; total: number };
const p: Pedido = { id: 1, total: 99.9 };
console.log("rodou:", p.id, p.total);

$ node t.ts
rodou: 1 99.9

Sem instalar nada. Sem flag. O Node 24 lê o arquivo, apaga as anotações de tipo e executa o JavaScript que sobra.

A partir de qual versão isso funciona

"Node 22" não é resposta suficiente: o suporte entrou no meio da série. Testei quatro versões:

versão node arquivo.ts sem flag
v22.5.1 ✗ — a flag --experimental-strip-types nem existe
v22.6.0 ✗ — só funciona com a flag
v22.17.1 ✗ — ainda precisa da flag
v22.18.0 ✓ funciona
v24.19.0 (LTS) ✓ funciona

Ou seja: 22.18 é a fronteira. Se a sua máquina tem um 22 mais antigo, ou você atualiza, ou continua com a flag.

⚠️ Mas ele não checa os tipos

Esta é a parte que quase todo tutorial esquece, e ela muda como você trabalha. O Node apaga os tipos — ele não os verifica. Veja:

$ cat erro.ts
const n: number = "isso e string";
console.log("rodou mesmo com erro de tipo:", n);

$ node erro.ts
rodou mesmo com erro de tipo: isso e string

Um erro de tipo escancarado, e o programa roda. O mesmo arquivo, no compilador:

$ npx tsc --noEmit erro.ts
erro.ts(1,7): error TS2322: Type 'string' is not assignable to type 'number'.

A conclusão prática: o node arquivo.ts substitui o ts-node para executar, mas não substitui o tsc para verificar. Você continua precisando do TypeScript instalado e de um passo de checagem — no editor, no pre-commit ou no CI.

O que o Node não consegue apagar

Só sintaxe apagável funciona. enum, namespace e decorators geram código de verdade, então não passam:

$ node en.ts
enum Status { Novo, Pago }
^^^^^^^^^^^^^^^^^^^^^^^^^^
SyntaxError

Dá para ligar a transformação completa:

$ node --experimental-transform-types en.ts
1

Mas a saída melhor é não usar essa sintaxe. Coloque erasableSyntaxOnly no tsconfig e o compilador te avisa antes de virar erro em produção:

$ npx tsc --noEmit --erasableSyntaxOnly en.ts
en.ts(1,6): error TS1294: This syntax is not allowed when 'erasableSyntaxOnly' is enabled.

Em vez de enum, use um objeto as const — é apagável e dá o mesmo resultado.


Montando a API

1. O projeto

mkdir minha-api && cd minha-api
npm init -y
npm install express
npm install -D typescript @types/node @types/express

Repare no que não foi instalado: nada de ts-node, tsx ou nodemon.

2. O passo que quebra todo mundo

Abra o package.json e acrescente "type": "module". Sem isso, a primeira linha de import derruba tudo:

Warning: Failed to load the ES module: /app/src/server.ts.
Make sure to set "type": "module" in the nearest package.json

import express from "express";
^^^^^^
SyntaxError: Cannot use import statement outside a module

Esse erro é o mais comum na configuração e a mensagem não é óbvia: parece problema de TypeScript, mas é do sistema de módulos.

3. O tsconfig.json

{
  "compilerOptions": {
    "target": "esnext",
    "module": "nodenext",
    "moduleResolution": "nodenext",
    "strict": true,
    "erasableSyntaxOnly": true,
    "verbatimModuleSyntax": true,
    "noEmit": true,
    "skipLibCheck": true
  },
  "include": ["src"]
}

Duas opções merecem explicação:

  • erasableSyntaxOnly — impede que você escreva a sintaxe que o Node não apaga. É o que evita descobrir o problema só no deploy.
  • noEmit: true — o TypeScript aqui não gera arquivo, só confere. Quem executa é o Node. Se você vai empacotar para produção, aí sim troca por um build.

4. A API

import express, { type Request, type Response, type NextFunction } from "express";

type Pedido = { id: number; cliente: string; total: number };
const pedidos: Pedido[] = [{ id: 1, cliente: "Ana", total: 249.9 }];

const app = express();
app.use(express.json());

app.get("/pedidos/:id", (req: Request, res: Response) => {
  const id = Number(req.params.id);
  const pedido = pedidos.find((p) => p.id === id);
  if (!pedido) return res.status(404).json({ erro: "pedido nao encontrado" });
  res.json(pedido);
});

app.post("/pedidos", (req: Request, res: Response) => {
  const { cliente, total } = req.body as Partial<Pedido>;
  if (!cliente || typeof total !== "number") {
    return res.status(400).json({ erro: "cliente e total sao obrigatorios" });
  }
  const novo: Pedido = { id: pedidos.length + 1, cliente, total };
  pedidos.push(novo);
  res.status(201).json(novo);
});

app.use((err: Error, _req: Request, res: Response, _next: NextFunction) => {
  res.status(500).json({ erro: err.message });
});

app.listen(3000, () => console.log("no ar em 3000"));

O type antes de Request não é enfeite: com verbatimModuleSyntax ligado, importar tipo sem essa palavra vira erro. É o que impede o tipo de sobrar no JavaScript final.

5. Rodando

$ node src/server.ts
no ar em 3000

E funcionando:

$ curl localhost:3000/pedidos/1
{"id":1,"cliente":"Ana","total":249.9}

$ curl -X POST localhost:3000/pedidos \
    -H "Content-Type: application/json" \
    -d '{"cliente":"Bruno","total":80}'
{"id":2,"cliente":"Bruno","total":80}

$ curl -i localhost:3000/pedidos/99
HTTP/1.1 404 Not Found

Durante o desenvolvimento, acrescente --watch e o Node reinicia sozinho a cada salvamento:

node --watch src/server.ts

Express 5: o erro em rota async agora funciona

No Express 4, um throw dentro de handler async não chegava no middleware de erro: a promise rejeitava sozinha e a requisição ficava pendurada. Por isso existia o express-async-errors.

No Express 5 isso é nativo. Testei com esta rota:

app.get("/quebra", async (_req, _res) => {
  throw new Error("estourei dentro de um async");
});

O resultado:

$ curl -i localhost:3000/quebra
HTTP/1.1 500 Internal Server Error

$ curl localhost:3000/pedidos/1
{"id":1,"cliente":"Ana","total":249.9}

O erro virou 500 pelo middleware, e o servidor continuou de pé. Se o seu projeto tem express-async-errors no package.json, ele virou dependência morta.

⚠️ O Express 5 tem outras mudanças que quebram código de v4 — o padrão de rota com * mudou, req.param() saiu, e alguns middlewares antigos não foram atualizados. Não migre projeto grande sem ler o guia oficial.


O que você pode apagar do projeto antigo

dependência ainda precisa?
ts-node não, se o alvo é Node 22.18+
tsx não, para o caso comum
nodemon não, use node --watch
express-async-errors não, com Express 5
typescript sim — é ele que checa os tipos
@types/node, @types/express sim

Validação: o que este guia não cobre

Esta API aceita qualquer JSON e confere no braço, com if. Para projeto real você quer validação por schema — Zod é o padrão hoje, e ele deriva o tipo TypeScript do schema, então tipo e validação não saem de sincronia.

Também ficou de fora: banco de dados, autenticação e testes. Sobre a camada de segurança que toda API precisa antes de ir pro ar, escrevi o básico que quase ninguém faz. Se o Node ainda não está instalado na sua máquina, comece pelo guia do NVM, que cobre Linux, macOS e Windows. E se você está decidindo qual versão adotar, o que mudou no Node 22 ajuda.

Perguntas frequentes

O Node.js roda TypeScript nativamente?

Roda. Sem flag a partir do 22.18 na série 22 e do 23.6 na série 23 — no Node 24 LTS já é o padrão. Mas ele apenas apaga as anotações de tipo, não verifica nada.

Ainda preciso do ts-node ou do tsx em 2026?

Para executar, não — se o seu alvo é Node 22.18 ou superior. O node arquivo.ts cobre o caso comum. Você ainda precisa do pacote typescript instalado, porque é o tsc que checa os tipos.

O Node verifica os tipos quando roda um .ts?

Não. Um arquivo com const n: number = "texto" roda sem reclamar. A checagem continua sendo trabalho do tsc --noEmit, no editor ou no CI.

Por que dá "Cannot use import statement outside a module"?

Falta "type": "module" no package.json. É o erro mais comum ao montar o projeto, e a mensagem confunde porque parece problema de TypeScript quando é do sistema de módulos.

Por que meu enum não funciona com node arquivo.ts?

Porque enum não é sintaxe apagável — ele gera código em tempo de execução. Ou você roda com --experimental-transform-types, ou troca por um objeto as const. Ligar erasableSyntaxOnly no tsconfig avisa antes.

Preciso de express-async-errors no Express 5?

Não. No Express 5 um erro lançado dentro de handler async chega ao middleware de erro sozinho. Testado: a requisição devolve 500 e o servidor continua no ar.

Qual versão do Node usar para uma API nova?

A LTS, hoje a v24.19.0. Ela tem o suporte a TypeScript estável e o ciclo de manutenção mais longo.

Newsletter

Receba os artigos novos por e-mail

Sem spam. Só o aviso quando sai um tutorial ou artigo novo.