Configuração e tsconfig
O tsconfig.json controla como o compilador TypeScript transpila seu código. Uma boa configuração previne bugs e habilita as melhores features.
// tsconfig.json — configuração recomendada para Node.js moderno
{
"compilerOptions": {
"target": "ES2022", // features modernas de JS
"module": "NodeNext", // ESM nativo no Node
"moduleResolution": "NodeNext",
"outDir": "./dist",
"rootDir": "./src",
"strict": true, // SEMPRE true — habilita todas as checks
"noUncheckedIndexedAccess": true, // array[0] é T | undefined
"exactOptionalPropertyTypes": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"paths": { "@/*": ["./src/*"] }, // alias de importação
"skipLibCheck": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}strictNullChecks, strictFunctionTypes, noImplicitAny, strictPropertyInitialization e mais. Nunca desative — encontra bugs reais.Tipos Básicos e Primitivos
// Primitivos
let name: string = 'Kaique';
let age: number = 28;
let active: boolean = true;
let data: unknown = fetchData(); // use unknown, não any
let id: string | number = getId(); // union type
// Arrays
const nums: number[] = [1, 2, 3];
const strs: Array<string> = ['a', 'b']; // equivalente
const tuple: [string, number] = ['nome', 42]; // tamanho e tipos fixos
// Object types inline
const user: { name: string; email: string; age?: number } = {
name: 'Kaique', email: '[email protected]'
};
// Literal types
type Direction = 'north' | 'south' | 'east' | 'west';
type Status = 200 | 201 | 400 | 404 | 500;
// null e undefined com strictNullChecks
function find(id: string): User | null { ... }
const user = find('123');
if (user !== null) {
console.log(user.name); // TypeScript sabe que não é null aqui
}
// never — tipo de funções que nunca retornam
function throwError(msg: string): never {
throw new Error(msg);
}
// void — funções sem retorno significativo
function log(msg: string): void {
console.log(msg);
}Interfaces vs Type Aliases
Ambos descrevem formatos de objetos — a escolha entre eles é questão de convenção, mas há diferenças importantes.
// Interface — abre para extensão, melhor para objetos públicos de API
interface User {
id: string;
name: string;
email: string;
}
// Herança de interface
interface AdminUser extends User {
role: 'admin';
permissions: string[];
}
// Type alias — mais poderoso, permite union/intersection/mapped types
type ID = string | number;
type NullableUser = User | null;
type UserWithTimestamps = User & { createdAt: Date; updatedAt: Date };
// Quando usar cada um:
// Interface: shapes de objetos, especialmente em APIs públicas e classes
// Type: unions, intersections, utility types, primitives, anything complex
// Diferença crucial: interfaces são abertas (mergeáveis)
interface Config { host: string }
interface Config { port: number } // válido! merge automático
// → Config agora tem { host, port }Generics
Generics permitem escrever código reutilizável que funciona com múltiplos tipos sem perder type safety.
// Generic básico
function first<T>(arr: T[]): T | undefined {
return arr[0];
}
const n = first([1, 2, 3]); // TypeScript infere: T = number
const s = first(['a', 'b']); // T = string
// Generic com constraint
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const user = { name: 'Kaique', age: 28 };
const name = getProperty(user, 'name'); // string
const age = getProperty(user, 'age'); // number
// getProperty(user, 'foo') // ❌ Erro! 'foo' não existe em user
// Generic em classes
class Stack<T> {
private items: T[] = [];
push(item: T) { this.items.push(item); }
pop(): T | undefined { return this.items.pop(); }
peek(): T | undefined { return this.items[this.items.length - 1]; }
}
// Generic em interfaces — repositório genérico
interface Repository<T, ID = string> {
findById(id: ID): Promise<T | null>;
findAll(): Promise<T[]>;
create(data: Omit<T, 'id'>): Promise<T>;
update(id: ID, data: Partial<T>): Promise<T | null>;
delete(id: ID): Promise<boolean>;
}
// API Result pattern
type Result<T, E = Error> =
| { ok: true; value: T }
| { ok: false; error: E };
async function safeAsync<T>(fn: () => Promise<T>): Promise<Result<T>> {
try {
return { ok: true, value: await fn() };
} catch (e) {
return { ok: false, error: e as Error };
}
}Utility Types
TypeScript inclui utility types poderosos que transformam tipos existentes sem escrever código duplicado.
interface User {
id: string;
name: string;
email: string;
password: string;
createdAt: Date;
}
// Partial<T> — todos os campos opcionais
type UserUpdate = Partial<User>;
// { id?: string; name?: string; ... }
// Required<T> — todos os campos obrigatórios
type StrictUser = Required<User>;
// Pick<T, K> — selecionar campos
type UserPublic = Pick<User, 'id' | 'name' | 'email'>;
// Omit<T, K> — excluir campos
type UserCreate = Omit<User, 'id' | 'createdAt'>;
type UserWithoutPassword = Omit<User, 'password'>;
// Readonly<T> — imutável
type ImmutableUser = Readonly<User>;
// Record<K, V> — dicionário tipado
type UserMap = Record<string, User>;
type HttpStatusMap = Record<200 | 201 | 400 | 404, string>;
// ReturnType<T> — tipo de retorno de função
async function getUsers(): Promise<User[]> { ... }
type UsersResult = Awaited<ReturnType<typeof getUsers>>; // User[]
// Parameters<T> — parâmetros de função
function createUser(name: string, email: string): User { ... }
type CreateParams = Parameters<typeof createUser>; // [string, string]
// Extract e Exclude
type Status = 'pending' | 'active' | 'blocked' | 'deleted';
type ActiveStatuses = Extract<Status, 'pending' | 'active'>; // 'pending' | 'active'
type EditableStatuses = Exclude<Status, 'deleted'>; // 'pending' | 'active' | 'blocked'
// NonNullable<T>
type MaybeUser = User | null | undefined;
type DefiniteUser = NonNullable<MaybeUser>; // UserType Guards e Narrowing
Type narrowing permite refinar tipos dentro de blocos condicionais, tornando código type-safe com tipos dinâmicos.
// typeof guard
function format(value: string | number): string {
if (typeof value === 'string') return value.toUpperCase(); // string aqui
return value.toFixed(2); // number aqui
}
// instanceof guard
function processError(e: unknown): string {
if (e instanceof Error) return e.message;
if (typeof e === 'string') return e;
return 'Unknown error';
}
// Discriminated union — padrão poderoso para modelagem
type Shape =
| { kind: 'circle'; radius: number }
| { kind: 'square'; side: number }
| { kind: 'rectangle'; width: number; height: number };
function area(shape: Shape): number {
switch (shape.kind) {
case 'circle': return Math.PI * shape.radius ** 2;
case 'square': return shape.side ** 2;
case 'rectangle': return shape.width * shape.height;
// TypeScript garante exhaustividade — se faltar um case, erro de compilação
}
}
// Custom type guard
interface Cat { meow(): void }
interface Dog { bark(): void }
type Animal = Cat | Dog;
function isCat(animal: Animal): animal is Cat {
return 'meow' in animal;
}
function makeSound(animal: Animal) {
if (isCat(animal)) animal.meow(); // TypeScript sabe que é Cat
else animal.bark(); // e aqui é Dog
}
// Assertion functions — narrowing via exceção
function assertNonNull<T>(val: T | null | undefined, msg?: string): asserts val is T {
if (val == null) throw new Error(msg ?? 'Expected non-null value');
}
const user = getUser(); // User | null
assertNonNull(user, 'User not found');
user.name; // ✅ TypeScript sabe que user é User agoraTipos Avançados e Mapped Types
// Template literal types
type EventName = 'click' | 'focus' | 'blur';
type EventHandler = `on${Capitalize<EventName>}`;
// 'onClick' | 'onFocus' | 'onBlur'
// Mapped types — transformar todas as propriedades
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};
// Conditional types
type IsArray<T> = T extends any[] ? true : false;
type Flatten<T> = T extends Array<infer Item> ? Item : T;
type FlatNum = Flatten<number[]>; // number
type FlatStr = Flatten<string>; // string (não é array)
// infer — extrair tipo de dentro de outro
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;
type PromiseResult = UnwrapPromise<Promise<string>>; // string
// Decorators (TypeScript 5+) — requer "experimentalDecorators": true
function log(target: any, key: string, descriptor: PropertyDescriptor) {
const original = descriptor.value;
descriptor.value = function (...args: any[]) {
console.log(`Calling ${key} with`, args);
return original.apply(this, args);
};
return descriptor;
}
class UserService {
@log
async createUser(data: CreateUserDto) { ... }
}Integração com Node.js e Ecossistema
// Tipos para Node.js
import type { IncomingMessage, ServerResponse } from 'http';
import type { Request, Response } from 'express'; // @types/express
// Variáveis de ambiente tipadas com Zod
import { z } from 'zod';
const Env = z.object({
PORT: z.coerce.number().default(3000),
DATABASE_URL: z.string(),
NODE_ENV: z.enum(['development', 'production', 'test'])
});
type Env = z.infer<typeof Env>; // inferir tipo do schema Zod
const env = Env.parse(process.env);
// Paths alias em runtime com tsx/tsup
// tsconfig paths + plugin para resolver em runtime
import { myUtil } from '@/lib/utils'; // @/* → src/*
// Módulo Declaration Merging — estender tipos de libs
declare module 'express' {
interface Request {
user?: User;
requestId?: string;
}
}
// Satisfies operator (TS 4.9+) — valida sem widening
const config = {
host: 'localhost',
port: 5432,
} satisfies Partial<DatabaseConfig>;
// config.port é number (não DatabaseConfig['port'])
// Build com tsup
// tsup.config.ts
import { defineConfig } from 'tsup';
export default defineConfig({
entry: ['src/index.ts'],
format: ['cjs', 'esm'],
dts: true,
sourcemap: true,
clean: true,
splitting: false,
});Ferramentas do Ecossistema
tsx
Executa .ts diretamente com Node.js. Ótimo para scripts e desenvolvimento.
tsup
Bundler zero-config para bibliotecas TS. Usa esbuild internamente.
Zod
Schema validation + inferência de tipos. Valida dados externos em runtime.
ts-reset
Corrige tipos estranhos do TypeScript (Array.filter, JSON.parse, etc.).
import '@total-typescript/ts-reset' muda JSON.parse de any para unknown e .filter(Boolean) de (T|null)[] para T[]. Pequeno import, grande ganho de segurança.