TypeScript

O que Todo Dev Deve Saber sobre Node.js com TypeScript: Configuração, tsx e ts-node na Prática

20 min de leitura

O que Todo Dev Deve Saber sobre Node.js com TypeScript: Configuração, tsx e ts-node na Prática

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 (TypeScript Compiler) para gerar arquivos , depois executar com . 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

<h2>Node.js com TypeScript: Configuração, tsx e ts-node na Prática</h2>

<p>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.</p>

<h2>Entendendo o Cenário</h2>

<h3>Por que TypeScript no Node.js?</h3>

<p>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:</p>

<ol>

<li><strong>Compilar manualmente</strong>: usar <code>tsc</code> (TypeScript Compiler) para gerar arquivos <code>.js</code>, depois executar com <code>node</code>.</li>

<li><strong>Executar diretamente com ts-node</strong>: carrega e executa arquivos TypeScript na memória sem gerar arquivos intermediários.</li>

<li><strong>Usar tsx (moderno)</strong>: similar ao ts-node, mas mais rápido, com melhor suporte a ECMAScript modules e sem necessidade de configurações complexas.</li>

</ol>

<p>A escolha certa impacta na velocidade do desenvolvimento e na experiência durante execução de scripts, testes e aplicações em produção.</p>

<h2>Configuração Inicial do Projeto</h2>

<h3>Criando o Projeto e Instalando Dependências</h3>

<p>Vamos começar do zero. Crie uma pasta para seu projeto e inicialize o Node.js:</p>

<pre><code class="language-bash">mkdir meu-projeto-typescript

cd meu-projeto-typescript

npm init -y</code></pre>

<p>Agora, instale as dependências necessárias. Você vai precisar do TypeScript, do ts-node para desenvolvimento, e do tsx como alternativa moderna:</p>

<pre><code class="language-bash">npm install --save-dev typescript ts-node @types/node tsx</code></pre>

<p>O pacote <code>@types/node</code> 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 <code>fs</code>, <code>http</code>, <code>path</code>, etc.</p>

<h3>Configurando o tsconfig.json</h3>

<p>O arquivo <code>tsconfig.json</code> é o coração da configuração TypeScript. Ele define como o compilador deve processar seus arquivos. Crie esse arquivo na raiz do projeto:</p>

<pre><code class="language-json">{

&quot;compilerOptions&quot;: {

&quot;target&quot;: &quot;ES2020&quot;,

&quot;module&quot;: &quot;commonjs&quot;,

&quot;lib&quot;: [&quot;ES2020&quot;],

&quot;outDir&quot;: &quot;./dist&quot;,

&quot;rootDir&quot;: &quot;./src&quot;,

&quot;strict&quot;: true,

&quot;esModuleInterop&quot;: true,

&quot;skipLibCheck&quot;: true,

&quot;forceConsistentCasingInFileNames&quot;: true,

&quot;resolveJsonModule&quot;: true,

&quot;declaration&quot;: true,

&quot;declarationMap&quot;: true,

&quot;sourceMap&quot;: true

},

&quot;include&quot;: [&quot;src/*/&quot;],

&quot;exclude&quot;: [&quot;node_modules&quot;, &quot;dist&quot;]

}</code></pre>

<p>Alguns pontos importantes aqui:</p>

<ul>

<li><strong>target</strong>: <code>ES2020</code> significa que seu código será compilado para JavaScript moderno. Se precisa suportar ambientes muito antigos, reduza esse valor.</li>

<li><strong>module</strong>: <code>commonjs</code> é o padrão do Node.js. Use <code>esnext</code> se preferir ES modules.</li>

<li><strong>strict</strong>: <code>true</code> ativa todas as verificações de tipo, forçando você a ser mais cuidadoso com o código.</li>

<li><strong>outDir</strong> e <strong>rootDir</strong>: definem onde estão seus arquivos TypeScript (src) e onde irão os compilados (dist).</li>

<li><strong>esModuleInterop</strong>: permite usar imports comuns como <code>import express from &#039;express&#039;</code> em vez de <code>import * as express</code>.</li>

</ul>

<p>Crie a pasta <code>src</code> onde seus arquivos TypeScript ficarão:</p>

<pre><code class="language-bash">mkdir src</code></pre>

<h2>Usando ts-node para Desenvolvimento</h2>

<h3>O que é ts-node?</h3>

<p>ts-node é um executor TypeScript para Node.js que compila e executa arquivos TypeScript em tempo real, sem gerar arquivos <code>.js</code> intermediários. É perfeito para desenvolvimento, testes rápidos e scripts ocasionais. Você já o instalou no passo anterior.</p>

<h3>Executando Arquivos com ts-node</h3>

<p>Crie um arquivo simples para testar. Abra <code>src/index.ts</code>:</p>

<pre><code class="language-typescript">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, &quot;João Silva&quot;, &quot;joao@example.com&quot;);

console.log(&quot;Usuário criado:&quot;, usuario);

console.log(Email: ${usuario.email}, Ativo: ${usuario.ativo});</code></pre>

<p>Agora execute usando ts-node:</p>

<pre><code class="language-bash">npx ts-node src/index.ts</code></pre>

<p>Você verá:</p>

<pre><code>Usuário criado: { id: 1, nome: &#039;João Silva&#039;, email: &#039;joao@example.com&#039;, ativo: true }

Email: joao@example.com, Ativo: true</code></pre>

<p>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.</p>

<h3>Configurando Scripts no package.json</h3>

<p>Para facilitar a execução, adicione scripts ao seu <code>package.json</code>:</p>

<pre><code class="language-json">{

&quot;scripts&quot;: {

&quot;dev&quot;: &quot;ts-node src/index.ts&quot;,

&quot;start&quot;: &quot;node dist/index.js&quot;,

&quot;build&quot;: &quot;tsc&quot;,

&quot;build:watch&quot;: &quot;tsc --watch&quot;

}

}</code></pre>

<p>Agora você pode rodar <code>npm run dev</code> em vez de digitar o comando completo. O script <code>build</code> compila seu TypeScript para JavaScript em <code>dist/</code>, útil para produção.</p>

<h3>Limitações do ts-node</h3>

<p>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.</p>

<h2>Usando tsx: A Alternativa Moderna</h2>

<h3>O que é tsx?</h3>

<p>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.</p>

<h3>Executando com tsx</h3>

<p>A sintaxe é praticamente idêntica ao ts-node:</p>

<pre><code class="language-bash">npx tsx src/index.ts</code></pre>

<p>Você receberá o mesmo resultado, mas a execução será mais rápida. Para aplicações maiores, essa diferença fica evidente.</p>

<h3>Migrando de ts-node para tsx</h3>

<p>Se já tem scripts usando ts-node, é trivial migrar. Crie um exemplo com lógica um pouco mais complexa. Crie <code>src/api-simulada.ts</code>:</p>

<pre><code class="language-typescript">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&lt;ResultadoBusca&gt; {

const inicio = Date.now();

// Simula uma busca em banco de dados

const produtosMock: Produto[] = [

{ id: 1, nome: &quot;Notebook Dell&quot;, preco: 3500, estoque: 5 },

{ id: 2, nome: &quot;Mouse Logitech&quot;, preco: 150, estoque: 20 },

{ id: 3, nome: &quot;Teclado Mecânico&quot;, preco: 450, estoque: 10 },

];

// Filtra por termo (simulado)

const produtosFiltrados = produtosMock.filter(p =&gt;

p.nome.toLowerCase().includes(termo.toLowerCase())

);

const tempo = Date.now() - inicio;

return {

produtos: produtosFiltrados,

total: produtosFiltrados.length,

tempo_ms: tempo

};

}

// Executar a busca

(async () =&gt; {

const resultado = await buscarProdutos(&quot;notebook&quot;);

console.log(&quot;Resultado da busca:&quot;, resultado);

})();</code></pre>

<p>Execute com tsx:</p>

<pre><code class="language-bash">npx tsx src/api-simulada.ts</code></pre>

<p>Saída esperada:</p>

<pre><code>Resultado da busca: {

produtos: [

{ id: 1, nome: &#039;Notebook Dell&#039;, preco: 3500, estoque: 5 }

],

total: 1,

tempo_ms: 2

}</code></pre>

<h3>Configurando tsx no package.json</h3>

<p>Atualize seus scripts para usar tsx:</p>

<pre><code class="language-json">{

&quot;scripts&quot;: {

&quot;dev&quot;: &quot;tsx src/index.ts&quot;,

&quot;dev:watch&quot;: &quot;tsx watch src/index.ts&quot;,

&quot;start&quot;: &quot;node dist/index.js&quot;,

&quot;build&quot;: &quot;tsc&quot;,

&quot;build:watch&quot;: &quot;tsc --watch&quot;

}

}</code></pre>

<p>A flag <code>watch</code> do tsx monitora mudanças no arquivo e o reexecuta automaticamente, ótimo para desenvolvimento iterativo.</p>

<h3>Quando Usar tsx vs ts-node</h3>

<ul>

<li><strong>tsx</strong>: Para a maioria dos casos modernos. É mais rápido, tem melhor suporte a ES modules, e requer menos configuração.</li>

<li><strong>ts-node</strong>: Para projetos legados com CommonJS pesado, ou quando você precisa de um suporte muito específico à sua stack.</li>

</ul>

<h2>Compilando para Produção</h2>

<h3>O Fluxo Desenvolvimento vs Produção</h3>

<p>Durante desenvolvimento, você executa TypeScript diretamente com tsx ou ts-node. Mas em produção, você não quer instalar <code>ts-node</code> ou <code>tsx</code> (isso aumentaria o tamanho das dependências). A solução é compilar TypeScript para JavaScript antes de fazer deploy.</p>

<h3>Compilando com tsc</h3>

<p>Execute o comando de build:</p>

<pre><code class="language-bash">npm run build</code></pre>

<p>Isso executa <code>tsc</code>, que lê seu <code>tsconfig.json</code> e compila todos os arquivos <code>.ts</code> em <code>src/</code> gerando <code>.js</code> em <code>dist/</code>. Verifique:</p>

<pre><code class="language-bash">ls -la dist/</code></pre>

<p>Você verá arquivos como <code>index.js</code>, <code>api-simulada.js</code>, etc. Abra um deles para ver como ficou o JavaScript gerado:</p>

<pre><code class="language-javascript">&quot;use strict&quot;;

Object.defineProperty(exports, &quot;__esModule&quot;, { value: true });

function criarUsuario(id, nome, email) {

return {

id,

nome,

email,

ativo: true

};

}

const usuario = criarUsuario(1, &quot;João Silva&quot;, &quot;joao@example.com&quot;);

console.log(&quot;Usuário criado:&quot;, usuario);</code></pre>

<p>Repare que os tipos foram removidos (TypeScript não precisa deles em runtime), mas a lógica permanece idêntica.</p>

<h3>Executando em Produção</h3>

<p>Em produção, você roda o JavaScript compilado:</p>

<pre><code class="language-bash">npm run build

npm start</code></pre>

<p>Isso executa <code>node dist/index.js</code> — puro JavaScript, sem overhead de compilação.</p>

<h3>Otimizações para Produção</h3>

<p>Para projetos maiores, considere:</p>

<ol>

<li><strong>Minificação</strong>: use ferramentas como <code>esbuild</code> ou <code>webpack</code> para reduzir o tamanho dos arquivos.</li>

<li><strong>Tree-shaking</strong>: remover código não utilizado.</li>

<li><strong>Source maps</strong>: gerar arquivos <code>.map</code> para facilitar debugging em produção (você já tem <code>sourceMap: true</code> no <code>tsconfig.json</code>).</li>

</ol>

<p>Exemplo com esbuild:</p>

<pre><code class="language-bash">npm install --save-dev esbuild</code></pre>

<p>Crie um script de build otimizado em <code>scripts/build.js</code>:</p>

<pre><code class="language-javascript">const esbuild = require(&#039;esbuild&#039;);

esbuild.build({

entryPoints: [&#039;src/index.ts&#039;],

bundle: true,

minify: true,

outfile: &#039;dist/index.min.js&#039;,

target: &#039;node16&#039;,

sourcemap: true,

platform: &#039;node&#039;

}).catch(() =&gt; process.exit(1));</code></pre>

<p>Execute com <code>node scripts/build.js</code> para gerar um arquivo minificado e otimizado.</p>

<h2>Exemplo Prático Completo: Uma Aplicação Express</h2>

<p>Para solidificar o aprendizado, vamos criar uma pequena API REST com Express. Instale o Express e seus tipos:</p>

<pre><code class="language-bash">npm install express

npm install --save-dev @types/express</code></pre>

<p>Crie <code>src/server.ts</code>:</p>

<pre><code class="language-typescript">import express, { Request, Response } from &#039;express&#039;;

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: &#039;Aprender TypeScript&#039;,

concluida: false,

dataCriacao: new Date(&#039;2024-01-15&#039;)

},

{

id: 2,

titulo: &#039;Dominar Node.js&#039;,

concluida: false,

dataCriacao: new Date(&#039;2024-01-20&#039;)

}

];

let proximoId = 3;

// Rota GET - listar todas as tarefas

app.get(&#039;/tarefas&#039;, (req: Request, res: Response) =&gt; {

res.json({

sucesso: true,

dados: tarefas,

total: tarefas.length

});

});

// Rota GET - obter uma tarefa por ID

app.get(&#039;/tarefas/:id&#039;, (req: Request, res: Response) =&gt; {

const { id } = req.params;

const tarefa = tarefas.find(t =&gt; t.id === parseInt(id));

if (!tarefa) {

res.status(404).json({

sucesso: false,

mensagem: &#039;Tarefa não encontrada&#039;

});

return;

}

res.json({

sucesso: true,

dados: tarefa

});

});

// Rota POST - criar nova tarefa

app.post(&#039;/tarefas&#039;, (req: Request, res: Response) =&gt; {

const { titulo } = req.body;

if (!titulo || typeof titulo !== &#039;string&#039;) {

res.status(400).json({

sucesso: false,

mensagem: &#039;Título é obrigatório e deve ser uma string&#039;

});

return;

}

const novaTarefa: Task = {

id: proximoId++,

titulo,

concluida: false,

dataCriacao: new Date()

};

tarefas.push(novaTarefa);

res.status(201).json({

sucesso: true,

mensagem: &#039;Tarefa criada com sucesso&#039;,

dados: novaTarefa

});

});

// Rota PUT - atualizar tarefa

app.put(&#039;/tarefas/:id&#039;, (req: Request, res: Response) =&gt; {

const { id } = req.params;

const { titulo, concluida } = req.body;

const tarefa = tarefas.find(t =&gt; t.id === parseInt(id));

if (!tarefa) {

res.status(404).json({

sucesso: false,

mensagem: &#039;Tarefa não encontrada&#039;

});

return;

}

if (titulo) tarefa.titulo = titulo;

if (typeof concluida === &#039;boolean&#039;) tarefa.concluida = concluida;

res.json({

sucesso: true,

mensagem: &#039;Tarefa atualizada com sucesso&#039;,

dados: tarefa

});

});

// Rota DELETE - deletar tarefa

app.delete(&#039;/tarefas/:id&#039;, (req: Request, res: Response) =&gt; {

const { id } = req.params;

const indice = tarefas.findIndex(t =&gt; t.id === parseInt(id));

if (indice === -1) {

res.status(404).json({

sucesso: false,

mensagem: &#039;Tarefa não encontrada&#039;

});

return;

}

const [tarefaDeletada] = tarefas.splice(indice, 1);

res.json({

sucesso: true,

mensagem: &#039;Tarefa deletada com sucesso&#039;,

dados: tarefaDeletada

});

});

// Rota de health check

app.get(&#039;/health&#039;, (req: Request, res: Response) =&gt; {

res.json({

status: &#039;ok&#039;,

timestamp: new Date().toISOString(),

uptime: process.uptime()

});

});

// Iniciar servidor

app.listen(PORT, () =&gt; {

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);

});</code></pre>

<p>Atualize o script de desenvolvimento no <code>package.json</code>:</p>

<pre><code class="language-json">{

&quot;scripts&quot;: {

&quot;dev&quot;: &quot;tsx watch src/server.ts&quot;,

&quot;start&quot;: &quot;node dist/server.js&quot;,

&quot;build&quot;: &quot;tsc&quot;

}

}</code></pre>

<p>Execute a aplicação:</p>

<pre><code class="language-bash">npm run dev</code></pre>

<p>Saída esperada:</p>

<pre><code>✓ Servidor rodando em http://localhost:3000

✓ Health check: http://localhost:3000/health

✓ Listar tarefas: GET http://localhost:3000/tarefas</code></pre>

<p>Teste as rotas com curl ou Insomnia:</p>

<pre><code class="language-bash"># Listar tarefas

curl http://localhost:3000/tarefas

Criar nova tarefa

curl -X POST http://localhost:3000/tarefas \

-H &quot;Content-Type: application/json&quot; \

-d &#039;{&quot;titulo&quot;:&quot;Estudar TypeScript avançado&quot;}&#039;

Obter tarefa por ID

curl http://localhost:3000/tarefas/1

Atualizar tarefa

curl -X PUT http://localhost:3000/tarefas/1 \

-H &quot;Content-Type: application/json&quot; \

-d &#039;{&quot;concluida&quot;:true}&#039;

Deletar tarefa

curl -X DELETE http://localhost:3000/tarefas/1</code></pre>

<p>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.</p>

<h2>Conclusão</h2>

<p>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, <strong>tsx é a escolha recomendada para projetos novos</strong>: é 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 <code>tsc</code> antes de fazer deploy em produção, removendo a dependência de executores de runtime e reduzindo o tamanho das dependências.</p>

<h2>Referências</h2>

<ul>

<li><a href="https://www.typescriptlang.org/docs/" target="_blank" rel="noopener noreferrer">TypeScript Official Documentation</a> — Documentação oficial do TypeScript com guias detalhados e referência da API.</li>

</ul>

<ul>

<li><a href="https://nodejs.org/en/docs/guides/nodejs-with-typescript/" target="_blank" rel="noopener noreferrer">Node.js TypeScript Guide</a> — Guia oficial do Node.js sobre TypeScript, mantido pelos mantenedores do projeto.</li>

</ul>

<ul>

<li><a href="https://github.com/esbuild-kit/tsx" target="_blank" rel="noopener noreferrer">tsx - TypeScript Execute</a> — Repositório oficial do tsx com exemplos, benchmarks e documentação completa.</li>

</ul>

<ul>

<li><a href="https://typestrong.org/ts-node/" target="_blank" rel="noopener noreferrer">ts-node Documentation</a> — Documentação oficial do ts-node com opções avançadas de configuração.</li>

</ul>

<ul>

<li><a href="https://expressjs.com/en/resources/middleware/body-parser.html" target="_blank" rel="noopener noreferrer">Express with TypeScript</a> — Guia de integração do Express com TypeScript, incluindo tipagem de middlewares.</li>

</ul>

<p>&lt;!-- FIM --&gt;</p>

Comentários

Mais em TypeScript

Dominando NestJS Avançado: Guards, Interceptors, Pipes e Exception Filters em Projetos Reais
Dominando NestJS Avançado: Guards, Interceptors, Pipes e Exception Filters em Projetos Reais

Introdução ao Middleware de NestJS NestJS é um framework robusto construído s...

Dominando Classes em TypeScript: Modificadores, Readonly e Parameter Properties em Projetos Reais
Dominando Classes em TypeScript: Modificadores, Readonly e Parameter Properties em Projetos Reais

Introdução aos Modificadores de Acesso em TypeScript Os modificadores de aces...

O que Todo Dev Deve Saber sobre Inversão de Dependência com TypeScript: tsyringe e InversifyJS
O que Todo Dev Deve Saber sobre Inversão de Dependência com TypeScript: tsyringe e InversifyJS

Compreendendo Inversão de Dependência A inversão de dependência é um princípi...