http.createServer() cria um servidor web a partir de uma função, o request handler, que o Node chama a cada requisição recebida com dois objetos: req (o método, a URL e os headers que o cliente enviou) e res (o que você devolve). Defina um status code e headers em res, depois chame res.end() com o body. server.listen(3000) começa a aceitar conexões na porta 3000.
A primeira requisição responde You asked for GET /hello?name=Ada, e o Console registra o mesmo método e a mesma URL. Envie DELETE /anything pela barra de requisição: o mesmo handler responde, porque um servidor http puro não tem rotas. Toda requisição passa pela única função.
Enviar JSON e definir o status code
res.writeHead(status, headers) define o status code e os headers em uma chamada. Para JSON, defina Content-Type: application/json e envie o resultado de JSON.stringify(); res.end() só aceita string ou Buffer.
GET /users?limit=1 retorna [{"id":1,"name":"Ada"}]. req.url é o caminho mais a query string, então new URL(req.url, 'http://localhost') separa em pathname e searchParams (a base só está ali porque URL precisa de um endereço absoluto). Envie GET /nothing e o status vira 404.
Rotas com um switch
Uma API pequena não precisa de mais que um switch no método e no caminho. Monte uma chave como 'GET /users' e dê a cada rota o seu próprio case:
Envie POST /users com o body {"name": "Grace"}: a resposta é 201 com {"id":2,"name":"Grace"}, e GET /users agora lista os dois usuários. Envie o mesmo POST com o body hello e você recebe 400 com {"error":"Body must be JSON"}.
Ler o body de um POST
O Node não interpreta o body por você. req é uma readable stream, e o body chega em pedaços pelos eventos data, depois end dispara quando ele está completo. O readBody() acima junta os pedaços em uma string e retorna uma promise, para o handler poder dar await. Como req é async iterable, este loop faz o mesmo trabalho:
let data = '';
for await (const chunk of req) data += chunk;
const body = JSON.parse(data);
Um servidor real também limita o tamanho (para de ler e responde 413 quando data.length passa de um limite) e confere req.headers['content-type'] antes de chamar JSON.parse.
Erros comuns
Esquecer o res.end(). Um caminho do código que define um status mas nunca encerra a resposta deixa o cliente esperando até dar timeout.
Definir um header depois do body. Depois que res.end() ou o primeiro res.write() rodou, os headers já foram enviados, e res.setHeader() lança ERR_HTTP_HEADERS_SENT. Use return quando enviar uma resposta antes do fim, como os exemplos fazem.
Comparar req.url com um caminho. req.url inclui a query string, então req.url === '/users' é falso para /users?limit=1. Compare o pathname.
Quando usar Express
O módulo http basta para um health check, um receptor de webhooks ou para aprender como o HTTP funciona. Quando você precisa de parâmetros de rota como /users/:id, parse de body JSON, middleware e tratamento de erros, acaba escrevendo seu próprio framework pequeno. O Express é esse framework, construído sobre os mesmos objetos req e res; o roteamento no Express mostra a mesma API com app.get() e app.post().
Perguntas frequentes
Qual a diferença entre res.write e res.end?
res.write() envia um pedaço do body e mantém a resposta aberta, então você pode chamar várias vezes. res.end() envia um último pedaço opcional e encerra a resposta. Toda resposta precisa de exatamente um res.end().
Como paro um servidor HTTP do Node?
Chame server.close(). Ele para de aceitar conexões novas na hora e roda o callback quando as abertas terminam, que é o que um desligamento limpo no SIGTERM precisa.
Como uso HTTPS com o módulo http?
Use o módulo https: https.createServer({ key, cert }, handler) recebe o mesmo handler mais uma chave privada e um certificado. Em produção, normalmente um proxy como o Nginx ou a plataforma de hospedagem termina o TLS, e seu servidor Node continua escutando em HTTP simples.
Em que porta um servidor Node deve escutar?
Qualquer porta livre acima de 1024 funciona sem permissão de admin; 3000 e 8080 são comuns em desenvolvimento. As plataformas de hospedagem geralmente informam a porta em uma variável de ambiente, então escreva server.listen(process.env.PORT || 3000).