Pular para o conteúdo
Cursos

DevClub

LógicaFront-endBack-endMobile

IA Club

IA na prática
Estudar programaçãoEstudar IA

Guia completo

Node.js: guia de back-end, APIs e roteiro de estudo

Use este mapa para aprender runtime, npm, módulos, HTTP, Express, testes e deploy em Node.js antes de construir uma API completa.

8 lições em trilha13 artigos no guiaAtualizado em agosto de 2026

Node.js é o JavaScript rodando fora do navegador. Com ele, a mesma linguagem que você usa para mexer numa página passa a atender requisições, ler arquivo e falar com banco de dados. A primeira rota que devolve JSON cabe em cinco linhas.

Este guia é o mapa do caminho inteiro: o que dá para construir, em que ordem estudar, qual das três formas de subir um servidor escolher — com as três medidas na mesma máquina — e onde as pessoas travam de verdade. O exemplo que atravessa tudo é a API de uma livraria de bairro, a Página Sete, a mesma que aparece nas lições da trilha de Node.

Um pré-requisito honesto antes de começar: você precisa de JavaScript. Variável, função, array, objeto e promise. Se algum desses nomes ainda é vago, comece pelo guia de JavaScript e volte aqui — Node não acrescenta sintaxe nenhuma à linguagem, só acrescenta o mundo em volta dela.

Uma oficina fora do navegador: o mapa mental do Node

No navegador, o JavaScript trabalha dentro de uma loja já montada: há janela, documento, botões e cliques. O Node leva a mesma pessoa para a oficina dos fundos. Lá não existe document, mas existem arquivo, rede, processo, porta e variável de ambiente. A linguagem é a mesma; as ferramentas disponíveis ao redor mudaram.

Tecnicamente, Node é um runtime: ele combina o motor V8 com APIs próprias e um modelo de entrada e saída assíncrona. Faça uma verificação simples enquanto lê o próximo exemplo: pergunte se cada operação dependeria de window ou do sistema operacional. A resposta já mostra se aquele código pertence ao navegador ou ao Node.

Node é bom em uma coisa muito específica: esperar. Esperar o banco responder, esperar o disco entregar o arquivo, esperar a API do gateway de pagamento voltar. Enquanto espera uma coisa, ele atende outras mil. É por isso que ele domina justamente estes quatro trabalhos:

API que fala JSON. O caso central desta trilha: receber uma requisição HTTP, consultar dados e devolver JSON com status coerente.

Ferramenta de linha de comando e automação. Ler uma planilha da livraria e jogar no banco, renomear três mil arquivos, gerar relatório todo dia às sete. Node substitui shell script com uma linguagem que você já conhece.

Servidor de tempo real. Chat, notificação, painel que atualiza sozinho. Muitas conexões abertas ao mesmo tempo, cada uma fazendo pouca coisa — o formato exato em que o Node é forte.

O ferramental do front-end. Vite, ESLint, Prettier, o próprio npm: tudo isso é Node. Mesmo quem só quer fazer interface já usa Node todo dia sem saber.

E existe o outro lado. Node é uma thread só para o seu código. Qualquer conta pesada e síncrona congela o servidor inteiro. Isso não é opinião, dá para medir. A livraria tem duas rotas, uma leve e um “relatório” que faz uma conta grande sem parar para respirar:

js
import express from 'express';

const app = express();

app.get('/livros', (req, res) => res.json({ total: 3 }));

app.get('/relatorio', (req, res) => {
  let soma = 0;
  for (let i = 0; i < 3e9; i += 1) soma += i; // conta pesada, e síncrona
  res.json({ soma });
});

app.listen(3010, () => console.log('livraria no ar'));

Agora o cliente: peço /livros sozinho, depois disparo o relatório e, cinquenta milissegundos depois, peço /livros de novo.

js
const cronometrar = async (rota) => {
  const inicio = performance.now();
  await fetch(`http://127.0.0.1:3010${rota}`).then((r) => r.json());
  return Math.round(performance.now() - inicio);
};

await cronometrar('/livros'); // aquecimento, para não medir a primeira conexão

console.log('/livros sozinho:', await cronometrar('/livros'), 'ms');

const relatorio = cronometrar('/relatorio');
await new Promise((r) => setTimeout(r, 50));
console.log('/livros durante o relatório:', await cronometrar('/livros'), 'ms');
console.log('/relatorio inteiro:', await relatorio, 'ms');
/livros sozinho: 1 ms /livros durante o relatório: 1727 ms /relatorio inteiro: 1778 ms

Um milissegundo virou 1.727. A rota leve não ficou mais lenta: ela ficou na fila, esperando o relatório soltar a thread. Um único cliente pedindo o relatório derrubou a experiência de todos os outros.

A regra que sai daí: processamento pesado de CPU — transcodificar vídeo, redimensionar mil imagens, treinar modelo — não mora dentro do processo que atende requisição. Ou vai para outra linguagem, ou vai para um worker separado, ou vira uma fila. O event loop do Node explica o mecanismo por trás desse número.

Node ou a linguagem de back-end que você já conhece

Se você está escolhendo agora, use um critério concreto: a linguagem do back-end acompanha o problema, a base existente e a equipe que vai mantê-la.

Node ganha quando o mesmo time — ou a mesma pessoa — cuida da tela e do servidor. Você escreve map e filter dos dois lados, compartilha a validação, copia um trecho de um lado para o outro e ele funciona. Para quem está saindo do front-end e quer entender o outro lado, é o caminho mais curto que existe.

Python costuma encaixar quando o projeto depende de bibliotecas de análise, ciência de dados ou treino de modelos. Java e C# fazem sentido quando a base e o time já usam seus ecossistemas; PHP, quando o produto vive em WordPress, Laravel ou outra base PHP. A pergunta útil não é qual linguagem “ganha”, e sim qual reduz risco no sistema que você realmente precisa entregar.

O que não é critério: velocidade bruta da linguagem. A sua primeira API vai gastar 40 ms conversando com o banco e 0,3 ms executando o seu código. Trocar de linguagem para ganhar esses 0,3 ms é otimizar a parte errada.

As sete etapas da trilha, na ordem em que doem menos

Esta é a ordem em que a trilha de Node foi escrita. Ela não é a ordem da documentação: é a ordem em que cada assunto para de doer, porque cada etapa só usa o que a anterior já resolveu. Cada uma termina com uma coisa que você consegue mostrar para alguém.

1. O terreno: o que é e onde roda

O que é Node.js separa a linguagem do ambiente — o que é JavaScript e o que é o Node por baixo. O event loop explica como uma thread só atende mil requisições, que é o modelo mental que vai explicar metade dos seus bugs depois. Instalar o Node com nvm evita o problema mais bobo da área: dois projetos que precisam de versões diferentes.

Entregável: um script que roda no seu terminal e imprime alguma coisa útil.

2. O projeto: npm, módulos e dependência

npm e package.json formam o cartório do projeto: o que está instalado, em que versão, e quais comandos existem. ESM ou CommonJS resolve a confusão entre import e require, que é a origem do erro Cannot find module na maior parte das vezes.

Entregável: um projeto com package.json, uma dependência instalada e um script npm run dev que funciona.

3. O sistema: arquivo e ambiente

Ler e escrever arquivo com fs/promises é onde o Node deixa de ser “JavaScript no terminal” e vira programa de verdade. E variáveis de ambiente são o assunto que separa quem sabe fazer deploy de quem não sabe — porque senha em código não é descuido, é incidente.

Entregável: um importador que lê um arquivo JSON e grava outro, com o caminho vindo de uma variável de ambiente.

4. O servidor, sem framework nenhum

Criar um servidor HTTP puro parece um desvio, e é a etapa que mais rende: quem viu req e res crus entende o Express em vez de decorar. No caminho aparece o EADDRINUSE, o primeiro erro de servidor da vida de todo mundo. Junto vêm os conceitos que o framework não ensina: o que é uma API REST, métodos HTTP e status code.

Entregável: a rota GET /livros respondendo JSON sem nenhuma dependência instalada.

5. Express: o framework que você vai usar

Aqui a produtividade muda de patamar. A primeira rota no Express, req.params, req.query, req.body, middleware e a ordem do next() — e o erro que nasce de responder duas vezes, Cannot set headers after they are sent. Depois o projeto começa a crescer e precisa de organização: express.Router para dividir por recurso, CORS para o front conseguir chamar e tratamento de erro centralizado.

Entregável: o CRUD completo da livraria, com rotas separadas por arquivo.

6. A API que aguenta o mundo real

Uma API que só funciona com dados certos não está pronta. Validar a entrada com Zod é o que impede que um preco: "muito caro" chegue ao banco. Upload de arquivo com multer coloca a capa do livro no ar. A sequência fecha com dois mecanismos comuns de segurança: hash de senha com bcrypt e autenticação com JWT.

Entregável: rota de cadastro, rota de login e uma rota que só responde com token válido.

7. Fechar o ciclo: teste e deploy

Testar rota com node:test e supertest mostra que o Node moderno já traz o test runner embutido, sem Jest. Deploy de API Node coloca a coisa numa URL pública, com porta, variável de ambiente e healthcheck. A comparação Axios ou fetch resolve a última pergunta: como consumir outra API de dentro da sua.

Entregável: a API da Página Sete rodando numa URL que outra pessoa consegue abrir.

Para juntar essas sete etapas num único repositório, siga o tutorial API Node com TypeScript, Express, Prisma e Docker. Ele começa no ambiente vazio e termina com banco, testes e smoke test do sistema.

http nativo, Express ou Fastify: a tabela de decisão medida

Essa é a pergunta que mais aparece, e quase sempre é respondida com opinião. Eu escrevi a mesma API da livraria nas três formas, na mesma máquina, e medi.

O código nativo, com o módulo node:http que já vem instalado:

js
import { createServer } from 'node:http';
import { livros } from './livros.mjs';

createServer((req, res) => {
  if (req.method === 'GET' && req.url === '/livros') {
    res.writeHead(200, { 'content-type': 'application/json; charset=utf-8' });
    res.end(JSON.stringify(livros));
    return;
  }
  res.writeHead(404, { 'content-type': 'application/json; charset=utf-8' });
  res.end(JSON.stringify({ erro: 'rota não encontrada' }));
}).listen(3010);

O mesmo, em Express:

js
import express from 'express';
import { livros } from './livros.mjs';

const app = express();
app.get('/livros', (req, res) => res.json(livros));
app.listen(3010);

E em Fastify:

js
import Fastify from 'fastify';
import { livros } from './livros.mjs';

const app = Fastify();
app.get('/livros', async () => livros);
await app.listen({ port: 3010 });

Onze linhas contra cinco contra cinco. A diferença cresce quando a API cresce: com GET /livros, GET /livros/:id, POST /livros e um 404 para rota desconhecida, a versão nativa foi para 36 linhas, o Express para 17 e o Fastify para 15. O que o framework vende não é velocidade — é roteamento, leitura de corpo e status code que você não escreve à mão.

Rodei os mesmos quatro pedidos contra as três versões, uma de cada vez:

bash
curl -s http://localhost:3010/livros/2
curl -s http://localhost:3010/livros/99
curl -s -X POST http://localhost:3010/livros \
  -H 'content-type: application/json' \
  -d '{"titulo":"Um Defeito de Cor","autor":"Ana Maria Gonçalves","preco":89.9}'
curl -s http://localhost:3010/catalogo
{"id":2,"titulo":"O Avesso da Pele","autor":"Jeferson Tenório","preco":49.9} {"erro":"livro não encontrado"} {"id":4,"titulo":"Um Defeito de Cor","autor":"Ana Maria Gonçalves","preco":89.9} {"erro":"rota não encontrada"}

Nativo e Express devolveram byte por byte isso aí. O Fastify bateu nas três primeiras e discordou da última, porque tem um 404 próprio de fábrica:

{"message":"Route GET:/catalogo not found","error":"Not Found","statusCode":404}

Guarde esse detalhe: o corpo do erro é parte do contrato da sua API, e cada framework tem um padrão diferente. Se você não escrever o seu, quem consome vai receber três formatos distintos conforme a rota erra de um jeito ou de outro.

Agora os números. Máquina: MacBook Pro com Apple M4 Pro, 24 GB, macOS 27, Node 24.16.0. Carga com autocannon 8.0.0, 50 conexões, 10 segundos, um aquecimento antes; o valor é a mediana de cinco execuções. O tempo de inicialização é a mediana de seis medições de process.uptime() no momento do listen, descartando a primeira.

critério node:http puro Express 5.2.1 Fastify 5.12.1
linhas até a primeira rota JSON 11 5 5
linhas com lista, item, POST e 404 36 17 15
pacotes instalados 0 68 49
node_modules em disco 0 3,8 MB 13 MB
npm ci com cache vazio 622 ms 551 ms
do node servidor.js até o listen 17,9 ms 34,6 ms 53,6 ms
requisições por segundo 90.464 64.225 97.737

Três coisas nessa tabela contrariam o que se costuma dizer por aí.

O Express tem mais pacotes e ocupa menos disco. São 68 dependências contra 49 do Fastify, e ainda assim 3,8 MB contra 13 MB. Contar pacote no npm ls não diz nada sobre peso: dentro do Fastify vêm ajv, fast-json-stringify e um logger completo — peças grandes, que só entram em ação quando você declara um schema. O Express traz muitos pacotinhos; o Fastify traz poucos pacotões.

O nativo perde para o Fastify. Escrever tudo à mão não é o teto de desempenho — o teto é o cuidado do código que você escreveu. O meu roteamento nativo monta um new URL(req.url, ...) a cada requisição para separar o caminho. Trocando isso por uma comparação de string direta, o mesmo servidor nativo foi para 96.288 req/s (mediana de cinco execuções), praticamente empatando com o Fastify. Ou seja: os 7% que faltavam não eram do node:http, eram meus.

A conta de 64 mil requisições por segundo. Esse é o pior dos três, e é mais do que a esmagadora maioria dos sistemas brasileiros vê num dia inteiro. Uma corrida completa, no terminal:

bash
npx autocannon -c 50 -d 10 -w 1 http://127.0.0.1:3010/livros
┌─────────┬──────┬──────┬───────┬──────┬─────────┬─────────┬───────┐ │ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ ├─────────┼──────┼──────┼───────┼──────┼─────────┼─────────┼───────┤ │ Latency │ 0 ms │ 0 ms │ 1 ms │ 1 ms │ 0.05 ms │ 0.31 ms │ 46 ms │ └─────────┴──────┴──────┴───────┴──────┴─────────┴─────────┴───────┘ ┌───────────┬─────────┬─────────┬─────────┬─────────┬───────────┬──────────┬─────────┐ │ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ ├───────────┼─────────┼─────────┼─────────┼─────────┼───────────┼──────────┼─────────┤ │ Req/Sec │ 60,735 │ 60,735 │ 64,543 │ 66,815 │ 64,222.55 │ 1,863.07 │ 60,708 │ ├───────────┼─────────┼─────────┼─────────┼─────────┼───────────┼──────────┼─────────┤ │ Bytes/Sec │ 28.7 MB │ 28.7 MB │ 30.5 MB │ 31.6 MB │ 30.4 MB │ 883 kB │ 28.7 MB │ └───────────┴─────────┴─────────┴─────────┴─────────┴───────────┴──────────┴─────────┘

707k requests in 11.01s, 334 MB read

Repare que essa medição é do meu código, sem banco de dados. Assim que entra um SELECT, a diferença entre os três frameworks vira ruído: o banco custa milissegundos e eles custam microssegundos.

Com os números na mesa, uma tabela de decisão:

o seu caso escolha por quê
entender HTTP por dentro, estudando node:http ver req e res crus uma vez economiza meses de mágica
primeiro CRUD e projeto de portfólio Express rota e middleware explícitos, com poucas decisões iniciais
API que já existe e precisa de mais vazão na mesma máquina Fastify 52% mais requisições por segundo aqui, com schema de resposta junto
script que roda e morre: cron, importador, robô nem um nem outro não precisa de servidor HTTP, precisa de um arquivo .js
time grande, contrato de API rígido e documentado Fastify o schema vira validação e documentação de graça

O critério por trás da tabela: escolha pelo tamanho da comunidade enquanto você aprende, e pelo número medido só quando tiver um número que justifique. Trocar de framework por causa de benchmark de blog, num projeto que atende duzentas pessoas por dia, é trabalho jogado fora.

As três decisões que definem a estrutura do projeto

Antes de escrever a segunda rota, três decisões economizam retrabalho. Elas não são gosto: cada uma tem um custo concreto se for tomada errada.

Primeira: ESM ou CommonJS. Escolha ESM (import/export) e declare "type": "module" no package.json. É o padrão da linguagem, é o que o resto do ecossistema está adotando e é o que evita o vaivém entre as duas sintaxes no mesmo projeto. O package.json da livraria fica assim:

json
{
  "name": "pagina-sete-api",
  "version": "1.0.0",
  "type": "module",
  "main": "src/servidor.js",
  "scripts": {
    "dev": "node --watch --env-file=.env src/servidor.js",
    "start": "node src/servidor.js",
    "test": "node --test"
  },
  "dependencies": {
    "express": "^5.2.1"
  }
}

Repare no script dev: --watch reinicia sozinho a cada salvamento e --env-file lê o .env — os dois são do próprio Node, sem nodemon nem dotenv. O src/servidor.js que esse script sobe é este:

js
import express from 'express';

const app = express();
const porta = process.env.PORT ?? 3010;

app.get('/saude', (req, res) => res.json({ ok: true, ambiente: process.env.NODE_ENV }));

app.listen(porta, (erro) => {
  if (erro) throw erro;
  console.log(`Página Sete no ar em http://localhost:${porta}`);
});

E, no Node 24, você nem precisa do npm para rodar o script — node --run dev basta. Num terminal sobe o servidor; no outro, o curl:

bash
node --run dev
Página Sete no ar em http://localhost:3010
bash
curl -s http://localhost:3010/saude
{"ok":true,"ambiente":"desenvolvimento"}

Segunda: onde a validação mora. Na borda, antes do handler. A rota recebe dados já confiáveis; quem duvida do formato é o middleware de validação, uma vez só. Espalhar if (!req.body.titulo) dentro de cada rota é o caminho garantido para uma delas esquecer.

Terceira: pasta por recurso, não por tipo. Este é o layout adotado no guia; ele mantém os arquivos de cada funcionalidade próximos:

text
src/
  servidor.js          sobe o app e nada mais
  rotas.js             junta os roteadores
  livros/
    livros.rotas.js    as rotas do recurso
    livros.servico.js  a regra de negócio
    livros.schema.js   a validação da entrada
  emprestimos/
    ...
  middlewares/
    erro.js            o tratador central

A alternativa comum — uma pasta controllers, uma services, uma models — funciona bem até o terceiro recurso. Depois disso, mexer no cadastro de livro significa abrir três pastas distantes, e ninguém consegue apagar uma funcionalidade inteira com confiança. Agrupando por recurso, a livraria e os empréstimos vivem em caixas separadas.

O tratador de erro merece um desenho, porque a ordem importa mais do que parece. No Express, a requisição atravessa os middlewares na ordem em que foram registrados, e o tratador de erro — o único com quatro parâmetros — precisa ser o último:

requisição GET /livros express.json() middleware app.use(log) middleware app.get '/livros' resposta 200 JSON app.use((erro, req, res, next)) registrado sempre por último resposta 500 JSON erro em qualquer etapa cai aqui

Os erros da primeira semana, e o que cada um está dizendo

Boa parte do aprendizado de back-end é aprender a ler mensagem de erro. Cinco delas cobrem quase tudo no começo, e cada uma tem um diagnóstico próprio.

ReferenceError: require is not defined in ES module scope aparece no dia em que você copia um exemplo antigo para um projeto com "type": "module":

js
const express = require('express');
console.log(typeof express);
file:///private/tmp/livraria-node/requerer.mjs:1 const express = require('express'); ^

ReferenceError: require is not defined in ES module scope, you can use import instead at file:///private/tmp/livraria-node/requerer.mjs:1:17 at ModuleJob.run (node:internal/modules/esm/module_job:439:25) Node.js v24.16.0

O erro literalmente entrega a correção: troque por import. A diferença inteira está no sistema de módulos escolhido pelo projeto.

ERR_MODULE_NOT_FOUND é o segundo mais comum, e diz exatamente qual pacote falta e quem pediu por ele:

Error [ERR_MODULE_NOT_FOUND]: Cannot find package 'cors' imported from /private/tmp/livraria-node/faltando.mjs at Object.getPackageJSONURL (node:internal/modules/package_json_reader:301:9) at packageResolve (node:internal/modules/esm/resolve:768:81) at moduleResolve (node:internal/modules/esm/resolve:859:18)

Na maior parte das vezes é npm install esquecido, ou o pacote instalado numa pasta e o arquivo rodando em outra.

Os outros três, em resumo, cada um com o seu artigo:

  • EADDRINUSE: address already in use — outro processo já está na porta. Quase sempre é o seu servidor de ontem, ainda vivo.
  • Cannot set headers after they are sent to the client — você respondeu duas vezes na mesma requisição, normalmente por falta de um return antes do res.json.
  • ECONNREFUSED — você pediu para uma porta em que ninguém está ouvindo. O banco não subiu, a URL está errada, ou o servidor morreu sem você notar.

O padrão útil: erro que começa com ERR_MODULE ou Cannot find é arquivo; erro com EADDR, ECONN ou ETIMEDOUT é rede; o resto é o seu código. Saber em qual das três famílias você caiu já corta o problema pela metade.

Onde as pessoas travam: assíncrono, ambiente e deploy

Depois de acompanhar muita gente fazendo essa travessia, os pontos de parada se repetem. São três, e nenhum deles é sintaxe.

Assíncrono. O código deixa de acontecer de cima para baixo, e o corpo continua achando que acontece. O sintoma clássico é usar o resultado antes de ele existir:

js
import express from 'express';
import { readFile } from 'node:fs/promises';

const app = express();

app.get('/estoque', (req, res) => {
  const dados = readFile('./estoque.json', 'utf8'); // faltou o await
  res.json(JSON.parse(dados));
});

app.listen(3010, () => console.log('livraria no ar em http://localhost:3010'));
SyntaxError: Unexpected token 'o', "[object Promise]" is not valid JSON at JSON.parse (<anonymous>) at file:///private/tmp/livraria-node/sem-await.mjs:8:17 at Layer.handleRequest (/private/tmp/livraria-node/node_modules/router/lib/layer.js:152:17) at next (/private/tmp/livraria-node/node_modules/router/lib/route.js:157:13)

"[object Promise]" is not valid JSON é uma das mensagens mais didáticas do Node: a promessa virou texto porque você tentou tratá-la como o valor pronto. Faltou await, e faltou async na função da rota. Se essa família de bug ainda te pega, revise async e await antes de continuar.

Ambiente. O segundo ponto de parada é a diferença entre a sua máquina e o servidor. O exemplo mais caro é a porta:

js
const servidor = app.listen(process.env.PORT, () => {
  console.log('porta pedida:', process.env.PORT);
  console.log('porta real:  ', servidor.address().port);
});
porta pedida: undefined porta real: 51954

Sem a variável PORT definida, o Node não dá erro: ele escolhe uma porta livre qualquer. Na sua máquina você nem percebe; na hospedagem, o healthcheck bate na porta esperada, não encontra ninguém, e o deploy é marcado como falho sem nenhuma mensagem que explique. Por isso process.env.PORT ?? 3010, com valor de reserva explícito.

Deploy. O terceiro. Dois detalhes derrubam mais gente do que qualquer configuração complicada.

O primeiro é o endereço de escuta. Ouvir em 127.0.0.1 funciona lindamente na sua máquina e some dentro de um contêiner:

js
const host = process.env.HOST ?? '127.0.0.1';
app.listen(3010, host, () => console.log('ouvindo em', host, 'porta 3010'));

Com host em 127.0.0.1, pedi a mesma rota pelo endereço da máquina na rede:

bash
curl -sS http://192.168.68.106:3010/saude
curl: (7) Failed to connect to 192.168.68.106 port 3010 after 2 ms: Couldn't connect to server

O mesmo servidor, ouvindo em 0.0.0.0, respondeu {"ok":true} na mesma URL. 127.0.0.1 aceita só quem vem de dentro da mesma máquina — e, num contêiner, o resto do mundo está sempre fora.

O segundo é mais traiçoeiro, e vale conhecer: no Express 5, a função que você passa para o listen também é o tratador de erro dele. O código-fonte registra o último argumento em server.once('error', done). Ou seja:

js
app.listen(3010, (erro) => {
  if (erro) {
    console.error('não subiu:', erro.code);
    process.exit(1);
  }
  console.log('livraria no ar em http://localhost:3010');
});
não subiu: EADDRINUSE

Sem esse if (erro), o mesmo programa imprime “livraria no ar em http://localhost:3010” com a porta ocupada e o servidor não no ar — foi exatamente o que aconteceu aqui quando escrevi o exemplo. O servidor HTTP puro não faz isso: ele estoura com EADDRINUSE e você fica sabendo. Se a sua mensagem de sucesso aparece e a API não responde, é aqui que você olha.

O que você não precisa aprender agora

Metade da ansiedade de quem entra no back-end vem de uma lista de tecnologias que alguém jurou ser obrigatória. Adiar as coisas certas é o que mantém o estudo possível.

TypeScript. Vale muito, e vale depois de duas ou três APIs escritas em JavaScript puro. Tipos resolvem um problema de projeto grande; antes de sentir esse problema, eles só dobram a quantidade de erro que você precisa entender.

Docker e Kubernetes. Docker aparece naturalmente no deploy, e um Dockerfile de dez linhas resolve. Kubernetes é ferramenta de time de infraestrutura, não de quem está fazendo a primeira API.

Microserviços. A decisão de quebrar um sistema em vários serviços resolve um problema organizacional de empresa com muitos times. Num projeto de uma pessoa, ela só multiplica o número de coisas que podem quebrar.

GraphQL, gRPC, WebSocket. Cada um resolve um caso específico. Comece pelo contrato HTTP e REST desta trilha; depois compare quando requisitos como assinatura em tempo real, streaming ou consultas flexíveis aparecerem.

ORM antes de SQL. Prisma e Mongoose são ótimos e escondem exatamente o que você precisa aprender. Escreva SELECT e JOIN na mão primeiro; o ORM depois vira conforto em vez de mágica.

O que vale antecipar é Git, mesmo básico. Não pela entrevista: porque é o que transforma “consegui” em “consigo voltar quando quebrar”.

Como saber que você já sabe Node o suficiente

Não existe prova, mas existe um teste honesto. Se você consegue fazer estas sete coisas sem consultar tutorial, está pronto para se candidatar:

  1. Subir um projeto do zero: npm init, "type": "module", script dev com --watch, e uma rota respondendo JSON.
  2. Explicar a diferença entre 200, 201, 400, 401, 404 e 500 — e escolher o certo sem pensar muito.
  3. Ler o corpo de um POST, validar os campos e recusar o que estiver errado com uma mensagem que ajuda quem chamou.
  4. Dividir a API em arquivos por recurso, com um roteador por recurso e um tratador de erro só.
  5. Guardar segredo em variável de ambiente e explicar por que ele não pode ir para o repositório.
  6. Ler um stack trace inteiro e apontar a linha do seu arquivo no meio das linhas de node_modules.
  7. Colocar a API numa URL pública e mandar o link para outra pessoa abrir.

Se travou em algum item, ele é a sua próxima etapa — e cada um deles tem uma lição na trilha.

O que estudar depois: banco, filas e TypeScript

Com a API de pé, três direções fazem sentido, nesta ordem.

Banco de dados. É o próximo assunto obrigatório, e é maior que Node. Modelar tabela, escrever consulta, entender índice e transação. A trilha de banco de dados mostra o caminho do SQL até o Prisma, e é ele que transforma o array em memória da livraria em dado que sobrevive a um reinício.

Filas e trabalho em segundo plano. Lembra do relatório que congelou o servidor por 1,7 segundo? A resposta profissional é tirar esse trabalho da requisição: a rota registra o pedido, devolve 202 e um processo separado executa. É o assunto que separa API de brinquedo de API de produção.

TypeScript. Agora sim. Depois de duas APIs em JavaScript, os tipos deixam de ser burocracia e viram o que evita o undefined chegar no banco. O mapa da trilha de TypeScript segue a mesma lógica.

Para começar hoje, na ordem certa, entre pela trilha de Node do zero — cada lição tem código executado, a saída real e o erro mais comum daquele assunto. Se você quer só a primeira vitória rápida, pule direto para a primeira rota no Express e volte para o começo depois: ver a coisa respondendo JSON é o que sustenta as outras seis etapas.

Trilha

Node e APIs

JavaScript no servidor: rotas, banco de dados, autenticação e a API que o front consome.

Ver a trilha
  1. 01O que é Node.js e para que serve no back-end
  2. 02Instalar o Node.js com nvm e trocar de versão por projeto
  3. 03npm e package.json: instalar pacote, semver e scripts
  4. 04ESM ou CommonJS no Node: import, require e o campo type
  5. 05Ler e escrever arquivo no Node com fs/promises e path
  6. 06Variáveis de ambiente no Node: process.env e o arquivo .env
  7. 07Criar um servidor HTTP no Node sem framework nenhum
  8. 08Deploy de API Node: porta, env, healthcheck e Dockerfile

api node

node

Perguntas frequentes

Preciso saber JavaScript antes de aprender Node?
Precisa, e não é formalidade. Node não acrescenta sintaxe nenhuma à linguagem: ele acrescenta acesso a disco, rede e processo. Quem chega sem função, array, objeto e promise passa a trilha inteira brigando com o JavaScript e achando que está brigando com o Node.
Quanto tempo leva para colocar a primeira API no ar?
Depende da base de JavaScript e do tempo de prática. Meça o progresso por marcos: primeiro uma rota local, depois validação e erros, banco, testes e só então deploy. A rota é pequena; transformar essa rota numa API pública confiável é o projeto completo.
Express ainda vale a pena em 2026?
Vale como primeiro framework porque deixa rota, middleware e resposta HTTP visíveis sem impor uma arquitetura grande. No benchmark local deste guia ele entregou cerca de 64 mil requisições por segundo; esse número não é promessa para outro hardware, apenas prova de que a escolha inicial deve priorizar clareza e necessidades do projeto, não uma corrida abstrata.
Preciso de banco de dados desde a primeira semana?
Não. Comece guardando os dados num array em memória e depois num arquivo JSON. O banco entra quando você já entendeu rota, status code e validação — senão são dois assuntos difíceis ao mesmo tempo, e o erro de um parece erro do outro.
Node aguenta produção de verdade?
Aguenta quando o trabalho principal é esperar por rede e disco. O que compromete a capacidade é conta pesada e síncrona no mesmo processo que atende requisição: uma única conta de um segundo e meio para todo mundo, como o exemplo medido neste guia mostra.
Devo aprender TypeScript junto com Node?
Junto, não. Escreva duas ou três APIs em JavaScript puro primeiro. Tipo resolve um problema de projeto grande, e antes de sentir esse problema o TypeScript só duplica a quantidade de erro que você precisa entender.

O código deste guia foi executado em Node 24.16.0, Express 5.2.1 e Fastify 5.12.1, e as saídas exibidas são as reais — como produzimos este conteúdo.

Fontes consultadas

  1. Node.js Docs — HTTP — nodejs.org
  2. Express 5 — Roteamento — expressjs.com
  3. Fastify — Documentation — fastify.dev
  4. Node.js — Release schedule (LTS) — github.com