TypeScript-First: Por que Todo Projeto Node.js Começa com TypeScript em 2025
Olá HaWkers, se você ainda está criando projetos Node.js novos com JavaScript puro em 2025, preciso te contar: você está nadando contra a maré. TypeScript-first tornou-se o padrão absoluto da indústria, e com razão.
A pergunta não é mais "devo usar TypeScript?" mas sim "por que eu não usaria TypeScript?". Vamos explorar essa mudança fundamental que está definindo o desenvolvimento moderno.
A Revolução TypeScript-First em Node.js
Em 2025, a maioria esmagadora dos novos projetos Node.js começa com TypeScript. Isso não é apenas uma tendência passageira - é uma mudança fundamental na forma como desenvolvemos aplicações backend.
Os Números Não Mentem
// Estatísticas de adoção em 2025
const typescriptAdoption = {
newProjects: '87%', // Projetos novos usando TS
migrations: '45%', // Projetos JS sendo migrados
jobRequirements: '92%', // Vagas exigindo TS
npmPackages: '76%' // Top packages com tipos
};
// Principais frameworks já são TypeScript-first
const frameworkSupport = {
nestjs: 'TypeScript nativo',
fastify: 'Tipos first-class',
trpc: 'TypeScript-only',
prisma: 'TypeScript-first ORM',
nextjs: 'TypeScript recomendado'
};
Por Que TypeScript Venceu?
1. Suporte Nativo do Node.js
Node.js agora tem suporte experimental para executar TypeScript diretamente:
// Execute TypeScript diretamente sem compilação!
// node --experimental-strip-types server.ts
import express from 'express';
import type { Request, Response } from 'express';
const app = express();
interface User {
id: string;
name: string;
email: string;
}
app.get('/api/users/:id', async (req: Request, res: Response) => {
const userId = req.params.id;
const user: User = await db.users.findUnique({
where: { id: userId }
});
if (!user) {
return res.status(404).json({ error: 'User not found' });
}
res.json(user);
});
app.listen(3000, () => {
console.log('Server running on port 3000');
});
2. Prevenção de Bugs em Tempo de Desenvolvimento
TypeScript pega erros que só apareceriam em produção com JavaScript:
// Exemplo real: API de pagamento
interface PaymentRequest {
amount: number;
currency: 'USD' | 'EUR' | 'BRL';
customerId: string;
metadata?: Record<string, string>;
}
class PaymentService {
async processPayment(request: PaymentRequest) {
// TypeScript força você a lidar com todos os casos
if (request.amount <= 0) {
throw new Error('Amount must be positive');
}
// Autocomplete te mostra apenas moedas válidas
const exchangeRate = this.getExchangeRate(request.currency);
return {
transactionId: generateId(),
amount: request.amount,
currency: request.currency,
status: 'processed' as const
};
}
private getExchangeRate(currency: PaymentRequest['currency']): number {
// Erro de compilação se tentar passar moeda inválida
const rates = {
USD: 1.0,
EUR: 1.1,
BRL: 0.2
};
return rates[currency];
}
}
// Uso - TypeScript valida em tempo de desenvolvimento
const service = new PaymentService();
// ✅ Correto
await service.processPayment({
amount: 100,
currency: 'USD',
customerId: 'cus_123'
});
// ❌ Erro de compilação: "JPY" não é moeda válida
await service.processPayment({
amount: 100,
currency: 'JPY', // TypeScript impede isso!
customerId: 'cus_123'
});
3. Refatoração Segura e Confiável
// Mudanças seguras em código grande
interface Product {
id: string;
name: string;
price: number;
category: string;
// Adiciona novo campo obrigatório
sku: string;
}
// TypeScript imediatamente mostra todos os lugares que
// precisam ser atualizados para incluir SKU
function createProduct(data: Omit<Product, 'id'>): Product {
return {
id: generateId(),
...data
};
}
// Erro de compilação se não passar SKU
const product = createProduct({
name: 'Laptop',
price: 999,
category: 'Electronics'
// Missing 'sku' - TypeScript não deixa passar!
});
Setup Moderno de TypeScript em 2025
Configuração Otimizada
// tsconfig.json - Configuração moderna e otimizada
{
"compilerOptions": {
"target": "ES2023",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"lib": ["ES2023"],
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
Package.json Moderno
{
"name": "modern-typescript-api",
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "tsx watch src/server.ts",
"build": "tsc",
"start": "node dist/server.js",
"test": "vitest",
"lint": "eslint src --ext .ts",
"format": "prettier --write \"src/**/*.ts\""
},
"dependencies": {
"fastify": "^4.25.0",
"@fastify/cors": "^8.5.0",
"zod": "^3.22.0"
},
"devDependencies": {
"@types/node": "^20.10.0",
"tsx": "^4.7.0",
"typescript": "^5.3.0",
"vitest": "^1.0.0",
"eslint": "^8.56.0",
"@typescript-eslint/parser": "^6.17.0",
"@typescript-eslint/eslint-plugin": "^6.17.0",
"prettier": "^3.1.0"
}
}
API Moderna com Fastify + TypeScript
// src/server.ts
import Fastify from 'fastify';
import cors from '@fastify/cors';
import { z } from 'zod';
const server = Fastify({
logger: true
});
await server.register(cors);
// Schema validation com Zod
const CreateUserSchema = z.object({
name: z.string().min(3),
email: z.string().email(),
age: z.number().int().positive()
});
type CreateUserInput = z.infer<typeof CreateUserSchema>;
// Route handlers tipados
server.post<{ Body: CreateUserInput }>(
'/users',
async (request, reply) => {
// Valida e parseia automaticamente
const userData = CreateUserSchema.parse(request.body);
const user = await db.users.create({
data: userData
});
return reply.code(201).send(user);
}
);
server.get('/users/:id', async (request, reply) => {
const { id } = request.params as { id: string };
const user = await db.users.findUnique({
where: { id }
});
if (!user) {
return reply.code(404).send({ error: 'User not found' });
}
return user;
});
const start = async () => {
try {
await server.listen({ port: 3000 });
console.log('Server listening on http://localhost:3000');
} catch (err) {
server.log.error(err);
process.exit(1);
}
};
start();
TypeScript com Prisma: Type Safety End-to-End
// prisma/schema.prisma
model User {
id String @id @default(cuid())
email String @unique
name String
posts Post[]
createdAt DateTime @default(now())
}
model Post {
id String @id @default(cuid())
title String
content String?
published Boolean @default(false)
author User @relation(fields: [authorId], references: [id])
authorId String
createdAt DateTime @default(now())
}
// src/repositories/user.repository.ts
import { PrismaClient, Prisma } from '@prisma/client';
const prisma = new PrismaClient();
export class UserRepository {
// Prisma gera tipos automaticamente!
async findById(id: string) {
return prisma.user.findUnique({
where: { id },
include: {
posts: {
where: { published: true }
}
}
});
}
async create(data: Prisma.UserCreateInput) {
return prisma.user.create({
data,
include: { posts: true }
});
}
async findWithPosts(options: {
skip?: number;
take?: number;
}) {
return prisma.user.findMany({
...options,
include: {
posts: {
orderBy: { createdAt: 'desc' }
}
}
});
}
}
// Types inferidos automaticamente pelo Prisma!
const repo = new UserRepository();
const user = await repo.findById('123');
// user tem tipo completo: User & { posts: Post[] }
Testes Tipados com Vitest
// src/services/user.service.test.ts
import { describe, it, expect, beforeEach } from 'vitest';
import { UserService } from './user.service';
describe('UserService', () => {
let service: UserService;
beforeEach(() => {
service = new UserService();
});
it('should create user with valid data', async () => {
const userData = {
name: 'John Doe',
email: 'john@example.com',
age: 30
};
const user = await service.createUser(userData);
expect(user).toBeDefined();
expect(user.id).toBeTruthy();
expect(user.email).toBe(userData.email);
});
it('should throw error for invalid email', async () => {
const userData = {
name: 'John Doe',
email: 'invalid-email',
age: 30
};
await expect(
service.createUser(userData)
).rejects.toThrow('Invalid email');
});
it('should find user by id', async () => {
const created = await service.createUser({
name: 'Jane Doe',
email: 'jane@example.com',
age: 25
});
const found = await service.findById(created.id);
expect(found).toEqual(created);
});
});
Vantagens Competitivas no Mercado
1. Código Mais Seguro e Manutenível
// Mudanças propagam automaticamente
type UserRole = 'admin' | 'user' | 'moderator';
function getUserPermissions(role: UserRole) {
// Se adicionar nova role, TypeScript força você a
// atualizar todos os lugares que usam roles
switch (role) {
case 'admin':
return ['read', 'write', 'delete', 'admin'];
case 'moderator':
return ['read', 'write', 'moderate'];
case 'user':
return ['read'];
// TypeScript garante que todos os casos estão cobertos
}
}
2. Melhor Experiência de Desenvolvimento
- Autocomplete inteligente - IDE sabe exatamente o que está disponível
- Refatoração segura - Rename functions/variables sem medo
- Documentação inline - Tipos são documentação viva
- Detecção de erros instantânea - Bugs pegos em segundos, não em produção
3. Colaboração em Time
// Outros desenvolvedores entendem seu código instantaneamente
interface CreateOrderDTO {
userId: string;
items: Array<{
productId: string;
quantity: number;
price: number;
}>;
shippingAddress: {
street: string;
city: string;
state: string;
zipCode: string;
country: string;
};
paymentMethod: 'credit_card' | 'debit_card' | 'pix';
}
// Não precisa ler documentação - tipos explicam tudo
function createOrder(data: CreateOrderDTO): Promise<Order> {
// Implementação...
}
Desafios e Como Superá-los
1. Curva de Aprendizado
Solução: Comece simples, adicione complexidade gradualmente
// Nível 1: Tipos básicos
function add(a: number, b: number): number {
return a + b;
}
// Nível 2: Interfaces
interface User {
id: string;
name: string;
}
// Nível 3: Generics
function findById<T>(items: T[], id: string): T | undefined {
return items.find(item => (item as any).id === id);
}
// Nível 4: Advanced types
type Nullable<T> = T | null;
type ReadOnly<T> = { readonly [K in keyof T]: T[K] };
2. Tempo de Compilação
Solução: Use ferramentas modernas como tsx
para desenvolvimento rápido
# Desenvolvimento instantâneo
tsx watch src/server.ts
# Build otimizado para produção
tsc --build --incremental
O Futuro é TypeScript-First
Em 2025, TypeScript não é opcional - é esperado. Empresas procuram desenvolvedores com TypeScript, projetos open source migram para TypeScript, e novas ferramentas nascem TypeScript-first.
Se você ainda não domina TypeScript, este é o momento de investir nessa habilidade. Não é apenas sobre adicionar tipos ao JavaScript - é sobre pensar diferente, sobre construir software mais robusto e manutenível.
Para entender melhor o ecossistema moderno de Node.js, recomendo: Serverless em 2025: Como Node.js Domina a Arquitetura Sem Servidor onde exploramos como TypeScript e Node.js juntos dominam serverless.
Bora pra cima! 🦅
💻 Domine JavaScript de Verdade
TypeScript é poderoso, mas ele é construído sobre JavaScript. Dominar JavaScript profundamente é essencial para usar TypeScript efetivamente.
Preparei um material completo para você dominar JavaScript:
Formas de pagamento:
- 3x de R$34,54 sem juros
- ou R$97,90 à vista