🟢Runtime

Guia Completo de Node.js

Node.js moderno: event loop, ESM vs CommonJS, async patterns, Hono, Zod, Vitest, Docker, deploy.

Progresso
0%

Event Loop e Fundamentos

Node.js é um runtime JavaScript baseado no V8 do Chrome, com arquitetura non-blocking I/O via event loop. Single-threaded, mas capaz de alta concorrência por não bloquear em I/O.

O Event Loop

// Fases do Event Loop (simplificado):
// timers → pending callbacks → idle → poll → check → close callbacks

console.log('1 - sync');

setTimeout(() => console.log('2 - setTimeout'), 0);
setImmediate(() => console.log('3 - setImmediate'));

Promise.resolve().then(() => console.log('4 - microtask'));

console.log('5 - sync');

// Output: 1 → 5 → 4 → 2 → 3
// Microtasks (Promise) sempre antes de macrotasks (setTimeout)

Por que Node.js é rápido para I/O

  • Operações de I/O (rede, disco) são delegadas ao OS via libuv.
  • A thread principal nunca bloqueia esperando I/O — processa outros callbacks.
  • Exceção: operações CPU-intensivas bloqueiam o event loop inteiro.
Cuidado: Operações síncronas pesadas (hashing, parsing de JSON gigante, criptografia) bloqueiam o event loop. Use Worker Threads para CPU-bound work.

Versões e LTS

# Gerenciar versões com mise (recomendado) ou nvm
mise use node@22  # Node 22 LTS — atual recomendado
node --version    # v22.x.x

# Verificar se feature é suportada
node -e "console.log(process.version)"

Módulos — ESM vs CommonJS

Node.js suporta dois sistemas de módulos. ESM é o padrão moderno, mas CJS ainda é predominante no ecossistema legado.

CommonJS (CJS)

Padrão histórico. require() síncrono. Extensão .js ou .cjs.

ES Modules (ESM)

Padrão web. import/export. Extensão .mjs ou .js com "type":"module".

// CJS
const fs = require('fs');
const { join } = require('path');
module.exports = { myFunc };

// ESM
import fs from 'fs';
import { join } from 'path';
export { myFunc };

// package.json para ESM como padrão
{ "type": "module" }

// __dirname não existe em ESM — substituto:
import { fileURLToPath } from 'url';
import { dirname } from 'path';
const __dirname = dirname(fileURLToPath(import.meta.url));

Gerenciadores de Pacotes

# pnpm (recomendado — mais rápido e eficiente)
pnpm install, pnpm add, pnpm run

# npm (padrão)
npm install, npm run

# Bun (alternativa ultra-rápida — compatível com npm)
bun install, bun run

# Arquivo de lockfile — sempre commitar!
pnpm-lock.yaml / package-lock.json / bun.lock

Async/Await e Promises

JavaScript assíncrono com async/await torna o código não-bloqueante tão legível quanto código síncrono.

// Promise básica
function delay(ms: number): Promise<void> {
  return new Promise(resolve => setTimeout(resolve, ms));
}

// Async/await
async function fetchUser(id: string) {
  const user = await db.users.findById(id);     // não bloqueia event loop
  const posts = await db.posts.findByUser(id);  // aguarda anterior
  return { user, posts };
}

// Paralelo — muito mais rápido!
async function fetchUserData(id: string) {
  const [user, posts, comments] = await Promise.all([
    db.users.findById(id),
    db.posts.findByUser(id),
    db.comments.findByUser(id)
  ]);
  return { user, posts, comments };
}

// Error handling
async function safeCall<T>(fn: () => Promise<T>): Promise<[T | null, Error | null]> {
  try {
    return [await fn(), null];
  } catch (e) {
    return [null, e as Error];
  }
}
const [result, err] = await safeCall(() => fetchUser('123'));

Padrões Avançados

// Promise.allSettled — não falha se uma rejeitar
const results = await Promise.allSettled([p1, p2, p3]);
results.forEach(r => {
  if (r.status === 'fulfilled') console.log(r.value);
  else console.error(r.reason);
});

// Promise.race — resolve quando a mais rápida resolver
const fast = await Promise.race([fetch(url), timeout(5000)]);

// AbortController — cancelar fetch
const controller = new AbortController();
setTimeout(() => controller.abort(), 5000);
const res = await fetch(url, { signal: controller.signal });

HTTP e APIs REST

Node.js tem HTTP nativo, mas frameworks simplificam muito o desenvolvimento de APIs.

Hono — Framework Moderno (recomendado)

import { Hono } from 'hono';
import { zValidator } from '@hono/zod-validator';
import { z } from 'zod';

const app = new Hono();

// Middleware global
app.use('*', async (c, next) => {
  console.log(`${c.req.method} ${c.req.url}`);
  await next();
});

// Rota com validação
const CreateUser = z.object({ name: z.string().min(2), email: z.string().email() });

app.post('/users', zValidator('json', CreateUser), async (c) => {
  const body = c.req.valid('json');
  const user = await db.users.create(body);
  return c.json(user, 201);
});

app.get('/users/:id', async (c) => {
  const user = await db.users.findById(c.req.param('id'));
  if (!user) return c.json({ error: 'Not found' }, 404);
  return c.json(user);
});

export default app; // Funciona em Node, Bun, Deno, CF Workers

Express — Tradicional

import express from 'express';
const app = express();
app.use(express.json());

app.get('/health', (req, res) => res.json({ ok: true }));
app.listen(3000, () => console.log('Server on :3000'));

Fastify — Alta Performance

import Fastify from 'fastify';
const fastify = Fastify({ logger: true });

fastify.get('/ping', async () => ({ pong: true }));

await fastify.listen({ port: 3000, host: '0.0.0.0' });
Escolha: Hono para novos projetos (multi-runtime, type-safe, rápido). Express para projetos com ecossistema estabelecido. Fastify para máxima performance.

File System e Streams

import { readFile, writeFile, mkdir, readdir } from 'fs/promises';
import { createReadStream, createWriteStream } from 'fs';
import { pipeline } from 'stream/promises';

// Leitura assíncrona
const content = await readFile('data.json', 'utf-8');
const data = JSON.parse(content);

// Escrita com criação de diretório
await mkdir('output', { recursive: true });
await writeFile('output/result.json', JSON.stringify(data, null, 2));

// Streams — processar arquivos grandes sem carregar em memória
import { createGzip } from 'zlib';

await pipeline(
  createReadStream('big-file.csv'),
  createGzip(),
  createWriteStream('big-file.csv.gz')
);

// Listar diretório recursivamente (Node 22+)
for await (const entry of readdir('.', { recursive: true, withFileTypes: true })) {
  if (entry.isFile()) console.log(entry.name);
}
Streams:Sempre use streams para arquivos > 100MB. Carregá-los em memória com readFile pode crashar o processo por falta de memória.

Worker Threads e Child Processes

// Worker Threads — CPU-bound paralelo
import { Worker, isMainThread, parentPort, workerData } from 'worker_threads';

if (isMainThread) {
  const worker = new Worker(import.meta.url, {
    workerData: { numbers: [1, 2, 3, 4, 5] }
  });
  worker.on('message', result => console.log('Resultado:', result));
} else {
  const sum = workerData.numbers.reduce((a: number, b: number) => a + b, 0);
  parentPort!.postMessage(sum);
}

// Child Process — executar programa externo
import { execFile } from 'child_process';
import { promisify } from 'util';

const execFileAsync = promisify(execFile);
const { stdout } = await execFileAsync('git', ['log', '--oneline', '-10']);

Testes com Vitest

// vitest.config.ts
import { defineConfig } from 'vitest/config';
export default defineConfig({
  test: { environment: 'node', globals: true }
});

// user.test.ts
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { createUser, getUserById } from './user.service';

describe('User Service', () => {
  beforeEach(() => vi.clearAllMocks());

  it('cria usuário com dados válidos', async () => {
    const user = await createUser({ name: 'Kaique', email: '[email protected]' });
    expect(user.id).toBeDefined();
    expect(user.name).toBe('Kaique');
  });

  it('retorna null para usuário inexistente', async () => {
    const user = await getUserById('inexistente-id');
    expect(user).toBeNull();
  });

  it('mock de dependência externa', async () => {
    vi.mock('./db', () => ({ findUser: vi.fn().mockResolvedValue({ id: '1' }) }));
    const user = await getUserById('1');
    expect(user).toEqual({ id: '1' });
  });
});

Scripts de Test

"scripts": {
  "test": "vitest run",
  "test:watch": "vitest",
  "test:coverage": "vitest run --coverage",
  "test:ui": "vitest --ui"
}

Deploy com Docker

# Dockerfile otimizado para Node.js
FROM node:22-alpine AS base
WORKDIR /app
RUN npm install -g pnpm

# Instalar dependências
FROM base AS deps
COPY package.json pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile

# Build
FROM base AS builder
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN pnpm build

# Runner (imagem mínima)
FROM node:22-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
COPY --from=builder /app/dist ./dist
COPY --from=deps /app/node_modules ./node_modules
EXPOSE 3000
CMD ["node", "dist/index.js"]
# .dockerignore
node_modules
.git
dist
*.log
.env

Environment Variables

// src/config.ts — validação de env vars na inicialização
import { z } from 'zod';

const EnvSchema = z.object({
  NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),
  PORT: z.coerce.number().default(3000),
  DATABASE_URL: z.string().url(),
  API_KEY: z.string().min(16),
});

export const env = EnvSchema.parse(process.env);
// Falha rápido se alguma env var obrigatória estiver faltando
← Todos os treinamentos