Node.js com TypeScript: Configuração, tsx e ts-node na Prática
TypeScript é uma linguagem que adiciona tipagem estática ao JavaScript, permitindo capturar erros em tempo de desenvolvimento e melhorar a qualidade do código. Quando trabalhamos com Node.js, precisamos de ferramentas que executem nossos arquivos TypeScript diretamente, sem precisar compilar manualmente para JavaScript antes de cada execução. Este artigo vai te ensinar a configurar um projeto Node.js com TypeScript do zero, usando as ferramentas mais modernas e práticas do mercado.
Entendendo o Cenário
Por que TypeScript no Node.js?
Node.js executa JavaScript nativamente, mas TypeScript precisa ser transpilado (convertido) para JavaScript antes de ser executado. Sem as ferramentas certas, esse processo se torna tedioso e improdutivo. Existem basicamente três abordagens para resolver isso:
- Compilar manualmente: usar
tsc(TypeScript Compiler) para gerar arquivos.js, depois executar comnode. - Executar diretamente com ts-node: carrega e executa arquivos TypeScript na memória sem gerar arquivos intermediários.
- Usar tsx (moderno): similar ao ts-node, mas mais rápido, com melhor suporte a ECMAScript modules e sem necessidade de configurações complexas.
A escolha certa impacta na velocidade do desenvolvimento e na experiência durante execução de scripts, testes e aplicações em produção.
Configuração Inicial do Projeto
Criando o Projeto e Instalando Dependências
Vamos começar do zero. Crie uma pasta para seu projeto e inicialize o Node.js:
mkdir meu-projeto-typescript
cd meu-projeto-typescript
npm init -y
Agora, instale as dependências necessárias. Você vai precisar do TypeScript, do ts-node para desenvolvimento, e do tsx como alternativa moderna:
npm install --save-dev typescript ts-node @types/node tsx
O pacote @types/node fornece as definições de tipos para as APIs do Node.js, permitindo que você tenha autocompletar e verificação de tipos ao trabalhar com módulos nativos como fs, http, path, etc.
Configurando o tsconfig.json
O arquivo tsconfig.json é o coração da configuração TypeScript. Ele define como o compilador deve processar seus arquivos. Crie esse arquivo na raiz do projeto:
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"lib": ["ES2020"],
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
Alguns pontos importantes aqui:
- target:
ES2020significa que seu código será compilado para JavaScript moderno. Se precisa suportar ambientes muito antigos, reduza esse valor. - module:
commonjsé o padrão do Node.js. Useesnextse preferir ES modules. - strict:
trueativa todas as verificações de tipo, forçando você a ser mais cuidadoso com o código. - outDir e rootDir: definem onde estão seus arquivos TypeScript (src) e onde irão os compilados (dist).
- esModuleInterop: permite usar imports comuns como
import express from 'express'em vez deimport * as express.
Crie a pasta src onde seus arquivos TypeScript ficarão:
mkdir src
Usando ts-node para Desenvolvimento
O que é ts-node?
ts-node é um executor TypeScript para Node.js que compila e executa arquivos TypeScript em tempo real, sem gerar arquivos .js intermediários. É perfeito para desenvolvimento, testes rápidos e scripts ocasionais. Você já o instalou no passo anterior.
Executando Arquivos com ts-node
Crie um arquivo simples para testar. Abra src/index.ts:
interface Usuario {
id: number;
nome: string;
email: string;
ativo: boolean;
}
function criarUsuario(id: number, nome: string, email: string): Usuario {
return {
id,
nome,
email,
ativo: true
};
}
const usuario = criarUsuario(1, "João Silva", "joao@example.com");
console.log("Usuário criado:", usuario);
console.log(`Email: ${usuario.email}, Ativo: ${usuario.ativo}`);
Agora execute usando ts-node:
npx ts-node src/index.ts
Você verá:
Usuário criado: { id: 1, nome: 'João Silva', email: 'joao@example.com', ativo: true }
Email: joao@example.com, Ativo: true
A vantagem aqui é que TypeScript validou seus tipos em tempo de execução. Se você tentar passar um valor inválido, o ts-node vai reclamar antes de rodar o código.
Configurando Scripts no package.json
Para facilitar a execução, adicione scripts ao seu package.json:
{
"scripts": {
"dev": "ts-node src/index.ts",
"start": "node dist/index.js",
"build": "tsc",
"build:watch": "tsc --watch"
}
}
Agora você pode rodar npm run dev em vez de digitar o comando completo. O script build compila seu TypeScript para JavaScript em dist/, útil para produção.
Limitações do ts-node
Apesar de prático, ts-node tem algumas limitações. Ele é mais lento que executar JavaScript puro, pois faz a compilação a cada execução. Para aplicações com muitos arquivos, esse overhead fica visível. Além disso, em alguns casos com ES modules (imports modernos), pode haver comportamentos inesperados.
Usando tsx: A Alternativa Moderna
O que é tsx?
tsx é um executor TypeScript moderno, mais rápido que ts-node e com melhor suporte a ES modules. É mantido pelo criador do esbuild e traz performance superior. Você já instalou no projeto anterior.
Executando com tsx
A sintaxe é praticamente idêntica ao ts-node:
npx tsx src/index.ts
Você receberá o mesmo resultado, mas a execução será mais rápida. Para aplicações maiores, essa diferença fica evidente.
Migrando de ts-node para tsx
Se já tem scripts usando ts-node, é trivial migrar. Crie um exemplo com lógica um pouco mais complexa. Crie src/api-simulada.ts:
interface Produto {
id: number;
nome: string;
preco: number;
estoque: number;
}
interface ResultadoBusca {
produtos: Produto[];
total: number;
tempo_ms: number;
}
async function buscarProdutos(termo: string): Promise<ResultadoBusca> {
const inicio = Date.now();
// Simula uma busca em banco de dados
const produtosMock: Produto[] = [
{ id: 1, nome: "Notebook Dell", preco: 3500, estoque: 5 },
{ id: 2, nome: "Mouse Logitech", preco: 150, estoque: 20 },
{ id: 3, nome: "Teclado Mecânico", preco: 450, estoque: 10 },
];
// Filtra por termo (simulado)
const produtosFiltrados = produtosMock.filter(p =>
p.nome.toLowerCase().includes(termo.toLowerCase())
);
const tempo = Date.now() - inicio;
return {
produtos: produtosFiltrados,
total: produtosFiltrados.length,
tempo_ms: tempo
};
}
// Executar a busca
(async () => {
const resultado = await buscarProdutos("notebook");
console.log("Resultado da busca:", resultado);
})();
Execute com tsx:
npx tsx src/api-simulada.ts
Saída esperada:
Resultado da busca: {
produtos: [
{ id: 1, nome: 'Notebook Dell', preco: 3500, estoque: 5 }
],
total: 1,
tempo_ms: 2
}
Configurando tsx no package.json
Atualize seus scripts para usar tsx:
{
"scripts": {
"dev": "tsx src/index.ts",
"dev:watch": "tsx watch src/index.ts",
"start": "node dist/index.js",
"build": "tsc",
"build:watch": "tsc --watch"
}
}
A flag watch do tsx monitora mudanças no arquivo e o reexecuta automaticamente, ótimo para desenvolvimento iterativo.
Quando Usar tsx vs ts-node
- tsx: Para a maioria dos casos modernos. É mais rápido, tem melhor suporte a ES modules, e requer menos configuração.
- ts-node: Para projetos legados com CommonJS pesado, ou quando você precisa de um suporte muito específico à sua stack.
Compilando para Produção
O Fluxo Desenvolvimento vs Produção
Durante desenvolvimento, você executa TypeScript diretamente com tsx ou ts-node. Mas em produção, você não quer instalar ts-node ou tsx (isso aumentaria o tamanho das dependências). A solução é compilar TypeScript para JavaScript antes de fazer deploy.
Compilando com tsc
Execute o comando de build:
npm run build
Isso executa tsc, que lê seu tsconfig.json e compila todos os arquivos .ts em src/ gerando .js em dist/. Verifique:
ls -la dist/
Você verá arquivos como index.js, api-simulada.js, etc. Abra um deles para ver como ficou o JavaScript gerado:
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
function criarUsuario(id, nome, email) {
return {
id,
nome,
email,
ativo: true
};
}
const usuario = criarUsuario(1, "João Silva", "joao@example.com");
console.log("Usuário criado:", usuario);
Repare que os tipos foram removidos (TypeScript não precisa deles em runtime), mas a lógica permanece idêntica.
Executando em Produção
Em produção, você roda o JavaScript compilado:
npm run build
npm start
Isso executa node dist/index.js — puro JavaScript, sem overhead de compilação.
Otimizações para Produção
Para projetos maiores, considere:
- Minificação: use ferramentas como
esbuildouwebpackpara reduzir o tamanho dos arquivos. - Tree-shaking: remover código não utilizado.
- Source maps: gerar arquivos
.mappara facilitar debugging em produção (você já temsourceMap: truenotsconfig.json).
Exemplo com esbuild:
npm install --save-dev esbuild
Crie um script de build otimizado em scripts/build.js:
const esbuild = require('esbuild');
esbuild.build({
entryPoints: ['src/index.ts'],
bundle: true,
minify: true,
outfile: 'dist/index.min.js',
target: 'node16',
sourcemap: true,
platform: 'node'
}).catch(() => process.exit(1));
Execute com node scripts/build.js para gerar um arquivo minificado e otimizado.
Exemplo Prático Completo: Uma Aplicação Express
Para solidificar o aprendizado, vamos criar uma pequena API REST com Express. Instale o Express e seus tipos:
npm install express
npm install --save-dev @types/express
Crie src/server.ts:
import express, { Request, Response } from 'express';
interface Task {
id: number;
titulo: string;
concluida: boolean;
dataCriacao: Date;
}
const app = express();
const PORT = 3000;
// Middleware
app.use(express.json());
// Armazenamento em memória (em produção, use um banco de dados)
let tarefas: Task[] = [
{
id: 1,
titulo: 'Aprender TypeScript',
concluida: false,
dataCriacao: new Date('2024-01-15')
},
{
id: 2,
titulo: 'Dominar Node.js',
concluida: false,
dataCriacao: new Date('2024-01-20')
}
];
let proximoId = 3;
// Rota GET - listar todas as tarefas
app.get('/tarefas', (req: Request, res: Response) => {
res.json({
sucesso: true,
dados: tarefas,
total: tarefas.length
});
});
// Rota GET - obter uma tarefa por ID
app.get('/tarefas/:id', (req: Request, res: Response) => {
const { id } = req.params;
const tarefa = tarefas.find(t => t.id === parseInt(id));
if (!tarefa) {
res.status(404).json({
sucesso: false,
mensagem: 'Tarefa não encontrada'
});
return;
}
res.json({
sucesso: true,
dados: tarefa
});
});
// Rota POST - criar nova tarefa
app.post('/tarefas', (req: Request, res: Response) => {
const { titulo } = req.body;
if (!titulo || typeof titulo !== 'string') {
res.status(400).json({
sucesso: false,
mensagem: 'Título é obrigatório e deve ser uma string'
});
return;
}
const novaTarefa: Task = {
id: proximoId++,
titulo,
concluida: false,
dataCriacao: new Date()
};
tarefas.push(novaTarefa);
res.status(201).json({
sucesso: true,
mensagem: 'Tarefa criada com sucesso',
dados: novaTarefa
});
});
// Rota PUT - atualizar tarefa
app.put('/tarefas/:id', (req: Request, res: Response) => {
const { id } = req.params;
const { titulo, concluida } = req.body;
const tarefa = tarefas.find(t => t.id === parseInt(id));
if (!tarefa) {
res.status(404).json({
sucesso: false,
mensagem: 'Tarefa não encontrada'
});
return;
}
if (titulo) tarefa.titulo = titulo;
if (typeof concluida === 'boolean') tarefa.concluida = concluida;
res.json({
sucesso: true,
mensagem: 'Tarefa atualizada com sucesso',
dados: tarefa
});
});
// Rota DELETE - deletar tarefa
app.delete('/tarefas/:id', (req: Request, res: Response) => {
const { id } = req.params;
const indice = tarefas.findIndex(t => t.id === parseInt(id));
if (indice === -1) {
res.status(404).json({
sucesso: false,
mensagem: 'Tarefa não encontrada'
});
return;
}
const [tarefaDeletada] = tarefas.splice(indice, 1);
res.json({
sucesso: true,
mensagem: 'Tarefa deletada com sucesso',
dados: tarefaDeletada
});
});
// Rota de health check
app.get('/health', (req: Request, res: Response) => {
res.json({
status: 'ok',
timestamp: new Date().toISOString(),
uptime: process.uptime()
});
});
// Iniciar servidor
app.listen(PORT, () => {
console.log(`✓ Servidor rodando em http://localhost:${PORT}`);
console.log(`✓ Health check: http://localhost:${PORT}/health`);
console.log(`✓ Listar tarefas: GET http://localhost:${PORT}/tarefas`);
});
Atualize o script de desenvolvimento no package.json:
{
"scripts": {
"dev": "tsx watch src/server.ts",
"start": "node dist/server.js",
"build": "tsc"
}
}
Execute a aplicação:
npm run dev
Saída esperada:
✓ Servidor rodando em http://localhost:3000
✓ Health check: http://localhost:3000/health
✓ Listar tarefas: GET http://localhost:3000/tarefas
Teste as rotas com curl ou Insomnia:
# Listar tarefas
curl http://localhost:3000/tarefas
# Criar nova tarefa
curl -X POST http://localhost:3000/tarefas \
-H "Content-Type: application/json" \
-d '{"titulo":"Estudar TypeScript avançado"}'
# Obter tarefa por ID
curl http://localhost:3000/tarefas/1
# Atualizar tarefa
curl -X PUT http://localhost:3000/tarefas/1 \
-H "Content-Type: application/json" \
-d '{"concluida":true}'
# Deletar tarefa
curl -X DELETE http://localhost:3000/tarefas/1
A segurança de tipos do TypeScript trabalha aqui em múltiplas frentes: validação de tipos nos handlers, tipos explícitos para Request e Response, e verificação de tipos quando você acessa as tarefas.
Conclusão
Você aprendeu os três pilares essenciais para trabalhar com TypeScript em Node.js. Primeiro, compreender que TypeScript precisa de transpilação, e que existem ferramentas modernas para fazer isso de forma transparente durante desenvolvimento. Segundo, tsx é a escolha recomendada para projetos novos: é mais rápido, requer menos configuração que ts-node, e tem melhor suporte a ES modules — use-o para desenvolvimento e scripts. Terceiro, sempre compile seu TypeScript com tsc antes de fazer deploy em produção, removendo a dependência de executores de runtime e reduzindo o tamanho das dependências.
Referências
-
TypeScript Official Documentation — Documentação oficial do TypeScript com guias detalhados e referência da API.
-
Node.js TypeScript Guide — Guia oficial do Node.js sobre TypeScript, mantido pelos mantenedores do projeto.
-
tsx - TypeScript Execute — Repositório oficial do tsx com exemplos, benchmarks e documentação completa.
-
ts-node Documentation — Documentação oficial do ts-node com opções avançadas de configuração.
-
Express with TypeScript — Guia de integração do Express com TypeScript, incluindo tipagem de middlewares.