Dia 7 — Receber e validar dados do cliente
Ler o corpo da requisição
Ler o corpo da requisição
req.url e req.headers vêm prontos. O corpo não. Ele chega como um
stream: um pedaço de cada vez, na ordem em que a rede entregou. Quem monta o
body da requisição é o próprio servidor, ouvindo o evento data.
on('data'), concat, on('end')
function lerCorpo(req) { return new Promise((resolve, reject) => { const chunks = []; req.on('data', (pedaco) => chunks.push(pedaco)); req.on('end', () => resolve(Buffer.concat(chunks).toString('utf8'))); req.on('error', reject); }); }
Os chunks são Buffer, não string: o certo é juntar os buffers e converter
uma vez, no end. Fazer texto += pedaco.toString() converte a cada pedaço
e refaz a conversão do texto inteiro a cada rodada.
Para um corpo pequeno, o Node entrega tudo num data só. Para um corpo de
centenas de KB, ele quebra em vários — e a montagem é a mesma. Por isso o
content-length declarado e os bytes contados precisam bater: quando o cliente
manda menos do que promete, o end chega antes e o content-length é maior que
o corpo real.
Corpo vazio: JSON.parse('') lança
Este é o erro mais comum de API: a rota espera objeto, o cliente mandou nada, e
o JSON.parse do body estoura com um SyntaxError que não diz nada sobre o que
o cliente fez de errado.
if (!texto) return respostaDeErro(res, 400, 'corpo vazio'); try { const objeto = JSON.parse(texto); } catch (erro) { /* ... */ }
O teste do texto vem antes do parse, sempre.
Corpo que não é JSON: o erro.message diz a posição
JSON.parse lança SyntaxError cujo message traz o caractere em que o parse
parou. É a única pista de onde o texto virou lixo, e ela aponta o caracter exato.
Texto que parece JSON e não é quase sempre é aspas faltando ou vírgula sobrando.
O servidor não adivinha o tipo
O servidor não sabe se o corpo é JSON, formulário ou binário. Ele só sabe o que
o cabeçalho content-type diz — e o parse é decisão de cada rota. Dois
pedidos com o mesmo corpo e content-type diferentes chegam idênticos ao
servidor; o que muda o comportamento é a rota que os atende.
for await no corpo
req é um stream legível, então dá para percorrer direto:
for await (const pedaco of req) { total += pedaco.length; }
Esse for await é o que a documentação chama de async iterators: o for
normal espera uma lista pronta, e o for await espera um valor de cada vez,
entrega o controle e volta quando o próximo chega.
É a forma curta de montar corpo, e serve melhor quando ele é grande e não
precisa caber inteiro na memória. Quando precisa caber — porque o JSON.parse
vai exigir o texto todo —, o caminho é Buffer.concat mesmo.
Exemplo
'use strict'; // Exemplo da aula 1 do dia 7: montar o corpo da requisicao. // // `req` nao vem com o corpo pronto. Ele e um STREAM: um pedaco de cada vez, e // chega na ordem em que a rede entregou. Quem monta o corpo e o proprio // servidor, ouvindo o evento `data` e juntando os pedacos. // // A aula mostra o caminho inteiro: `on('data')` -> `concat` -> `on('end')` -> // `JSON.parse`, e o que acontece em cada degrau. E mostra os tres casos que // quebram servidor: corpo vazio, corpo que nao e JSON, e corpo que promete mais // do que manda (content-length mentiroso). const http = require('node:http'); async function main() { // --- 1. o listener de corpo, a versao que serve para sempre --- // // `chunks` e um array de BUFFER, e cada `data` entrega um pedaco. O `+=` de // string faria a conversao a cada pedaco e perderia performance; o certo e // juntar os buffers e converter uma vez, no `end`. function lerCorpo(req) { return new Promise((resolve, reject) => { const chunks = []; let bytes = 0; req.on('data', (pedaco) => { chunks.push(pedaco); bytes += pedaco.length; }); req.on('end', () => resolve({ bruto: Buffer.concat(chunks), bytes, pedacos: chunks.length })); req.on('error', reject); }); } const servidor = http.createServer(async (req, res) => { const { bruto, bytes, pedacos } = await lerCorpo(req); const texto = bruto.toString('utf8'); // O que chegou, em uma linha, e o que a aula precisa mostrar. if (req.url === '/inspecionar') { return res.end(JSON.stringify({ metodo: req.method, content_type: req.headers['content-type'] || null, content_length: req.headers['content-length'] || null, bytes_recebidos: bytes, pedacos_recebidos: pedacos, corpo_bruto: texto, }, null, 1)); } // O `content-length` declarado e o `bytes` contado: quando batem, o cliente // mandou tudo. Quando o cliente manda menos do que promete, o `end` chega // antes e o `content-length` e maior que o corpo real. if (req.url === '/json') { if (!texto) { res.writeHead(400, { 'Content-Type': 'application/json' }); return res.end(JSON.stringify({ erro: 'corpo vazio' })); } try { const objeto = JSON.parse(texto); res.writeHead(200, { 'Content-Type': 'application/json' }); return res.end(JSON.stringify({ recebido: objeto, campos: Object.keys(objeto) })); } catch (erro) { // O erro do `JSON.parse` tem posicao: e a unica pista de onde o texto // virou lixo, e ela diz o caracter exato. res.writeHead(400, { 'Content-Type': 'application/json' }); return res.end(JSON.stringify({ erro: 'JSON invalido', posicao: erro.message.match(/position (\d+)/)?.[1] || null, trecho: texto.slice(0, 40), })); } } res.writeHead(404); res.end('use /inspecionar ou /json'); }); await new Promise((resolve) => servidor.listen(0, '127.0.0.1', resolve)); const { port } = servidor.address(); const base = `http://127.0.0.1:${port}`; try { console.log('servidor no ar em ' + base); console.log('(a porta muda a cada execucao: o `listen(0)` pediu uma livre)'); // --- 1. um corpo pequeno, um pedaco so --- console.log(''); console.log('--- POST /inspecionar, corpo pequeno ---'); let r = await fetch(base + '/inspecionar', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ nm_aluno: 'Ana', turma: 3 }), }); console.log(await r.text()); // --- 2. um corpo que chega em VARIOS pedacos --- // // O `fetch` manda o corpo de uma vez para um corpo pequeno, e o Node junta // num `data` so. Para ver o stream de verdade, o exemplo monta um corpo // grande que o Node precisa quebrar em pedacos. O que muda na pratica e so // o numero de `data`: a montagem e a mesma. const grande = JSON.stringify({ nm_aluno: 'x'.repeat(300000) }); console.log(''); console.log('--- o MESMO corpo, agora com 300 KB ---'); r = await fetch(base + '/inspecionar', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: grande, }); const inspecionadoGrande = JSON.parse(await r.text()); console.log('content-length declarado:', inspecionadoGrande.content_length, '(vem como TEXTO)'); console.log('bytes recebidos: ', inspecionadoGrande.bytes_recebidos, '(contado, numero)'); console.log('pedacos recebidos: ', inspecionadoGrande.pedacos_recebidos, '<-- varios: o Node entregou em pedacos, e o servidor juntou todos'); console.log('os dois batem, depois de Number():', Number(inspecionadoGrande.content_length) === inspecionadoGrande.bytes_recebidos); console.log('o `content-length` e texto, e comparar direto com === daria false sempre;'); console.log('e a pegadinha e o espelho da aula 1 do dia 6: header chega string.'); // --- 3. corpo vazio --- // // `JSON.parse('')` LANCA. E o erro mais comum de API: rota que espera // objeto, cliente que mandou nada, e o `JSON.parse` explodindo com uma // mensagem que nao diz nada sobre o que o cliente fez de errado. console.log(''); console.log('--- POST /json sem corpo nenhum ---'); r = await fetch(base + '/json', { method: 'POST' }); console.log('status:', r.status, '| corpo:', await r.text()); console.log(''); console.log('o mesmo JSON.parse com texto vazio, fora do servidor:'); try { JSON.parse(''); } catch (erro) { console.error(erro.name + ': ' + erro.message); console.log(' erro:', erro.name, '-', erro.message, ' <- SyntaxError, sem posicao util'); } console.log('por isso a rota tem que testar o texto ANTES do parse.'); // --- 4. corpo que nao e JSON --- console.log(''); console.log('--- POST /json com texto que nao e JSON ---'); r = await fetch(base + '/json', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: '{ nm_aluno: Ana, turma: 3 }', }); console.log('status:', r.status, '| corpo:', await r.text()); console.log(''); console.log('o texto tem aspas duplas faltando: `nm_aluno: Ana` em vez de `"nm_aluno": "Ana"`'); console.log('o `erro.message` do JSON.parse traz a posicao, e ela diz onde o parse parou.'); // --- 5. o corpo chega CRU, sem o roteador adivinhar nada --- // // O servidor nao sabe se o corpo e JSON, formulario ou binario. Ele so sabe // o que o cabecalho `content-type` diz, e o parse e decisao de cada rota. console.log(''); console.log('--- content-type e a unica pista do tipo ---'); for (const [tipo, corpo] of [ ['application/json', '{"a":1}'], ['text/plain', '{"a":1}'], ['text/plain', 'texto simples'], ]) { r = await fetch(base + '/inspecionar', { method: 'POST', headers: { 'Content-Type': tipo }, body: corpo, }); const visto = JSON.parse(await r.text()); console.log(' content-type ' + tipo.padEnd(20) + ' corpo ' + JSON.stringify(corpo).padEnd(16) + ' -> mesmo texto recebido: ' + JSON.stringify(visto.corpo_bruto)); } console.log(''); console.log('os tres chegaram iguais. O parse e da rota, nao do servidor.'); // --- 6. `for await` no corpo --- // // O `req` e um stream legivel, entao da para percorrer com `for await`. // A forma e mais curta, e serve melhor quando o corpo e grande e nao // precisa caber inteiro na memoria. const servidor2 = http.createServer(async (req, res) => { let total = 0; let pedacos = 0; for await (const pedaco of req) { total += pedaco.length; pedacos += 1; } res.end(JSON.stringify({ total, pedacos, metodo: 'for await' })); }); await new Promise((resolve) => servidor2.listen(0, '127.0.0.1', resolve)); const base2 = `http://127.0.0.1:${servidor2.address().port}`; const r2 = await fetch(base2 + '/', { method: 'POST', body: 'x'.repeat(300000) }); const lido = JSON.parse(await r2.text()); console.log('--- for await no corpo do stream ---'); console.log(lido.total === 300000 && lido.pedacos > 1 ? 'o `for await` contou os mesmos bytes em ' + lido.pedacos + ' pedacos' : 'o `for await` contou ' + lido.total + ' bytes em ' + lido.pedacos + ' pedacos'); await new Promise((resolve) => servidor2.close(resolve)); console.log('o segundo servidor foi encerrado no fim, com close().'); } finally { await new Promise((resolve) => servidor.close(resolve)); console.log(''); console.log('servidor encerrado com close().'); } } main().catch((erro) => { console.error('falhou:', erro.code || erro.name, '-', erro.message); process.exit(1); });
Saída real
servidor no ar em http://127.0.0.1:37675
(a porta muda a cada execucao: o `listen(0)` pediu uma livre)
--- POST /inspecionar, corpo pequeno ---
{
"metodo": "POST",
"content_type": "application/json",
"content_length": "28",
"bytes_recebidos": 28,
"pedacos_recebidos": 1,
"corpo_bruto": "{\"nm_aluno\":\"Ana\",\"turma\":3}"
}
--- o MESMO corpo, agora com 300 KB ---
content-length declarado: 300015 (vem como TEXTO)
bytes recebidos: 300015 (contado, numero)
pedacos recebidos: 6 <-- varios: o Node entregou em pedacos, e o servidor juntou todos
os dois batem, depois de Number(): true
o `content-length` e texto, e comparar direto com === daria false sempre;
e a pegadinha e o espelho da aula 1 do dia 6: header chega string.
--- POST /json sem corpo nenhum ---
status: 400 | corpo: {"erro":"corpo vazio"}
o mesmo JSON.parse com texto vazio, fora do servidor:
erro: SyntaxError - Unexpected end of JSON input <- SyntaxError, sem posicao util
por isso a rota tem que testar o texto ANTES do parse.
--- POST /json com texto que nao e JSON ---
status: 400 | corpo: {"erro":"JSON invalido","posicao":"2","trecho":"{ nm_aluno: Ana, turma: 3 }"}
o texto tem aspas duplas faltando: `nm_aluno: Ana` em vez de `"nm_aluno": "Ana"`
o `erro.message` do JSON.parse traz a posicao, e ela diz onde o parse parou.
--- content-type e a unica pista do tipo ---
content-type application/json corpo "{\"a\":1}" -> mesmo texto recebido: "{\"a\":1}"
content-type text/plain corpo "{\"a\":1}" -> mesmo texto recebido: "{\"a\":1}"
content-type text/plain corpo "texto simples" -> mesmo texto recebido: "texto simples"
os tres chegaram iguais. O parse e da rota, nao do servidor.
--- for await no corpo do stream ---
o `for await` contou os mesmos bytes em 6 pedacos
o segundo servidor foi encerrado no fim, com close().
servidor encerrado com close().
Validar o que chegou
Validar o que chegou
Validar não é "testar se o campo existe". É dizer, campo por campo: o que é
obrigatório, que tipo tem que ter, qual o tamanho, o que o cliente ganhou por
mandar lixo. E é aqui que a API decide se responde 400 ou 500 — a diferença
entre "o cliente mandou errado" e "o servidor quebrou".
A forma: lista de regras
const REGRAS = [ { campo: 'nm_aluno', obrigatorio: true, teste: (v) => typeof v === 'string' && v.trim().length > 0, mensagem: 'nm_aluno e obrigatorio e tem que ser texto nao vazio' }, { campo: 'turma', teste: (v) => Number.isInteger(v), mensagem: 'turma e obrigatorio e tem que ser numero inteiro' }, ]; const erros = []; for (const regra of REGRAS) { if (dados[regra.campo] === undefined && !regra.obrigatorio) continue; if (!regra.teste(dados[regra.campo])) erros.push(regra); }
Cada regra declara o tipo esperado do campo (typeof v === 'string',
Number.isInteger(v)) e a mensagem que o cliente recebe quando ele não bate.
O tipo esperado vem antes do obrigatorio, e a ordem importa: um campo que não
veio reprova em qualquer tipo, e um campo que veio errado reprova só no tipo.
A função devolve todos os erros de uma vez, não só o primeiro. Isso é escolha
de API: quem errou três campos quer corrigir os três, não descobrir um por
requisição. O que ela entrega é um erro de validação por campo que reprovou, e
não um erro do servidor — a distinção é o que separa o status 400 do 500.
trim antes de validar: "veio" não é "tem conteúdo"
' ' passa em typeof === 'string' e reprova em .length > 0. A diferença
entre "o campo veio" e "o campo tem conteúdo" é a segunda que importa: um nome
de três espaços não é um nome.
O trim de entrada é o .trim(), e ele é aplicado ao valor que entra — antes
de comparar, para o teste não passar em dado que não tem nada dentro.
teste: (v) => typeof v === 'string' && v.trim().length > 0
Validar e sanear são dois passos
O trim acontece depois de validar, e o valor limpo é o que vai para o
banco. Validar responde "este dado é válido?"; sanear responde "como este dado
será gravado?". Fazer os dois de uma vez esconde uma decisão: o banco passa a
guardar texto normalizado sem que a validação tenha dito que normaliza.
A função que aplica essa limpeza é o que a documentação chama de sanitizar: ela
recebe o objeto já validado e devolve outro, com o valor gravável. O nome é de
higiene — é o passo que garante que o que foi validado é o que será salvo.
O corpo tem que ser objeto
JSON.parse aceita array, string e número. dados.nm_aluno em um array é
undefined — sem erro, só um 400 com mensagem de "campo obrigatório", que
aponta para o campo errado. O teste é typeof dados === 'object' && !Array.isArray(dados).
400 ou 422
| Código | RFC | Significa |
|---|---|---|
400 | 7231 | pedido malformado |
422 | 4918 | sintaxe ok, semântica errada |
Os dois servem. O material usa 400 porque é o que a maioria dos frameworks e
dos clientes já espera, e trocar o código no meio do curso quebra o cliente que
já estava escrito.
A mensagem de erro também é API
"nm_aluno e obrigatorio e tem que ser texto nao vazio" -> o cliente sabe o que fazer "TypeError: Cannot read properties of undefined" -> o cliente só trava
O formato é sempre o mesmo, e é ele que faz uma resposta de erro ser útil: o
status na linha de cabeçalho e o corpo com a lista do que reprovou, campo por
campo. Sem o corpo, o status 400 vira uma pista sem direção.
E o 500 é o caso oposto: quando a consulta ao banco estoura, não é 400, é
500 — e o detalhe técnico vai para o log, não para o corpo da resposta.
Um 500 que carrega SQL no corpo entrega o esquema do banco para quem deu o
erro.
Exemplo
'use strict'; // Exemplo da aula 2 do dia 7: validar o que chegou. // // Validar nao e "testar se o campo existe". E dizer, campo por campo: o que e // obrigatorio, que tipo tem que ter, qual o tamanho, o que o cliente ganhou por // mandar lixo. E o ponto em que a API decide se responde 400 ou 500 — e a // diferenca entre "o cliente mandou errado" e "o servidor quebrou". // // O exemplo sobe um servidor, manda cinco pedidos malformados de proposito e // mostra o status e o corpo de cada um. Cada resposta 400 diz o campo e o // motivo, e e isso que o cliente precisa ver para corrigir. const http = require('node:http'); async function main() { // --- 1. ler o corpo (aula 1 deste dia) --- function lerCorpo(req) { return new Promise((resolve, reject) => { const chunks = []; req.on('data', (p) => chunks.push(p)); req.on('end', () => resolve(Buffer.concat(chunks).toString('utf8'))); req.on('error', reject); }); } // --- 2. a funcao de validacao --- // // A forma: uma lista de REGRAS, cada uma com o nome do campo, o teste e a // mensagem. A funcao percorre e devolve TODOS os erros de uma vez — e nao so // o primeiro. Isso e escolha de API: quem errou tres campos quer corrigir os // tres, nao descobrir um por requisicao. const REGRAS = [ { campo: 'nm_aluno', obrigatorio: true, teste: (v) => typeof v === 'string' && v.trim().length > 0, mensagem: 'nm_aluno e obrigatorio e tem que ser texto nao vazio', }, { campo: 'nm_aluno', teste: (v) => v === undefined || v.trim().length <= 80, mensagem: 'nm_aluno pode ter no maximo 80 caracteres', }, { campo: 'turma', obrigatorio: true, teste: (v) => Number.isInteger(v), mensagem: 'turma e obrigatorio e tem que ser numero inteiro', }, { campo: 'turma', teste: (v) => v === undefined || (v >= 1 && v <= 12), mensagem: 'turma tem que estar entre 1 e 12', }, { campo: 'email', teste: (v) => v === undefined || v === null || /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(String(v)), mensagem: 'email tem que ter o formato [email protected]', }, ]; function validar(dados) { const erros = []; for (const regra of REGRAS) { const valor = dados[regra.campo]; // `undefined` em campo opcional: a regra nao obrigatoria e sempre passa. if (valor === undefined && !regra.obrigatorio) continue; if (!regra.teste(valor)) erros.push({ campo: regra.campo, mensagem: regra.mensagem }); } return erros; } // --- 3. o `trim` antes de validar --- // // `' '` passa em `typeof === 'string'` e reprova em `.length > 0`. A diferenca // entre "o campo veio" e "o campo tem conteudo", e e a segunda que importa: um // nome de tres espacios nao e um nome. console.log('--- trim antes de validar ---'); for (const bruto of ['Ana', ' ', '', ' Carlos ']) { console.log(' ' + JSON.stringify(bruto).padEnd(14) + ' -> trim: ' + JSON.stringify(bruto.trim()) + ' | length>0: ' + String(bruto.trim().length > 0).padEnd(5) + ' | sem trim: ' + String(bruto.length > 0)); } console.log(' sem trim, " " passa na validacao e grava tres espacios no banco.'); // --- 4. o servidor --- const servidor = http.createServer(async (req, res) => { const texto = await lerCorpo(req); if (!texto) { res.writeHead(400, { 'Content-Type': 'application/json' }); return res.end(JSON.stringify({ erro: 'corpo vazio' })); } let dados; try { dados = JSON.parse(texto); } catch (erro) { res.writeHead(400, { 'Content-Type': 'application/json' }); return res.end(JSON.stringify({ erro: 'JSON invalido' })); } // O corpo precisa ser um objeto. Um array ou uma string passam pelo // `JSON.parse` e nao tem campo nenhum — e `dados.nm_aluno` seria `undefined` // em vez de dar erro. if (typeof dados !== 'object' || dados === null || Array.isArray(dados)) { res.writeHead(400, { 'Content-Type': 'application/json' }); return res.end(JSON.stringify({ erro: 'o corpo tem que ser um objeto JSON' })); } const erros = validar(dados); if (erros.length > 0) { res.writeHead(400, { 'Content-Type': 'application/json' }); return res.end(JSON.stringify({ erro: 'validacao falhou', quantidade: erros.length, campos: erros })); } // Sanear: o `trim` acontece DEPOIS de validar, e o valor limpo e o que vai // para o banco. Validar e sanar sao passos diferentes. const limpo = { nm_aluno: dados.nm_aluno.trim(), turma: dados.turma, email: dados.email ? String(dados.email).trim().toLowerCase() : null, }; res.writeHead(201, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ id: 1, ...limpo, recebido_com_espaco: dados.nm_aluno !== limpo.nm_aluno })); }); await new Promise((resolve) => servidor.listen(0, '127.0.0.1', resolve)); const { port } = servidor.address(); const base = `http://127.0.0.1:${port}`; try { console.log(''); console.log('servidor no ar em ' + base); console.log('(a porta muda a cada execucao: o `listen(0)` pediu uma livre)'); // --- 5. os cinco pedidos, um por caso --- const casos = [ ['valido', '{"nm_aluno":"Ana","turma":3}'], ['sem nm_aluno', '{"turma":3}'], ['nm_aluno so com espaco', '{"nm_aluno":" ","turma":3}'], ['turma como texto', '{"nm_aluno":"Ana","turma":"3"}'], ['turma fora da faixa', '{"nm_aluno":"Ana","turma":99}'], ['email invalido', '{"nm_aluno":"Ana","turma":3,"email":"ana@"}'], ['erro em dois campos', '{"nm_aluno":"","turma":99}'], ['corpo que e array', '[{"nm_aluno":"Ana","turma":3}]'], ['corpo vazio', ''], ]; for (const [nome, corpo] of casos) { const r = await fetch(base + '/alunos', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: corpo, }); const resposta = await r.text(); console.log(''); console.log('--- ' + nome + ' -> ' + r.status + ' ---'); console.log(resposta.length > 220 ? resposta.slice(0, 220) + '...' : resposta); } // --- 6. 400 e nao 422: por que o material usa 400 --- // // A RFC 7231 diz 400 para "pedido malformado" e a 2019 criou o 422 para // "sintaxe boa, semantica ruim". Os dois servem; o material usa 400 porque // e o que a maioria dos frameworks e de cliente ja espera, e trocar o // codigo no meio do curso quebra o cliente que ja estava escrito. console.log(''); console.log('--- 400 ou 422 ---'); console.log('400 (RFC 7231): pedido malformado — o que o material usa'); console.log('422 (RFC 4918): sintaxe ok, semantica errada'); console.log('os dois servem; 400 e o que os frameworks e os clientes ja esperam'); // --- 7. o 500 e o caso oposto --- console.log(''); console.log('--- 500: quando o erro NAO e do cliente ---'); console.log('se a consulta ao banco estoura, nao e 400: e 500, e a mensagem'); console.log('tecnica vai para o LOG, nao para o cliente. O 500 nunca carrega'); console.log('detalhe de SQL: quem le o corpo da resposta pode ser o proprio atacante.'); // --- 8. nunca devolver o erro cru ao cliente --- console.log(''); console.log('--- a mensagem de erro tambem e API ---'); console.log('"nm_aluno e obrigatorio e tem que ser texto nao vazio" -> o cliente sabe o que fazer'); console.log('"TypeError: Cannot read properties of undefined" -> o cliente so trava'); } finally { await new Promise((resolve) => servidor.close(resolve)); console.log(''); console.log('servidor encerrado com close().'); } } main().catch((erro) => { console.error('falhou:', erro.code || erro.name, '-', erro.message); process.exit(1); });
Saída real
--- trim antes de validar ---
"Ana" -> trim: "Ana" | length>0: true | sem trim: true
" " -> trim: "" | length>0: false | sem trim: true
"" -> trim: "" | length>0: false | sem trim: false
" Carlos " -> trim: "Carlos" | length>0: true | sem trim: true
sem trim, " " passa na validacao e grava tres espacios no banco.
servidor no ar em http://127.0.0.1:33123
(a porta muda a cada execucao: o `listen(0)` pediu uma livre)
--- valido -> 201 ---
{"id":1,"nm_aluno":"Ana","turma":3,"email":null,"recebido_com_espaco":false}
--- sem nm_aluno -> 400 ---
{"erro":"validacao falhou","quantidade":1,"campos":[{"campo":"nm_aluno","mensagem":"nm_aluno e obrigatorio e tem que ser texto nao vazio"}]}
--- nm_aluno so com espaco -> 400 ---
{"erro":"validacao falhou","quantidade":1,"campos":[{"campo":"nm_aluno","mensagem":"nm_aluno e obrigatorio e tem que ser texto nao vazio"}]}
--- turma como texto -> 400 ---
{"erro":"validacao falhou","quantidade":1,"campos":[{"campo":"turma","mensagem":"turma e obrigatorio e tem que ser numero inteiro"}]}
--- turma fora da faixa -> 400 ---
{"erro":"validacao falhou","quantidade":1,"campos":[{"campo":"turma","mensagem":"turma tem que estar entre 1 e 12"}]}
--- email invalido -> 400 ---
{"erro":"validacao falhou","quantidade":1,"campos":[{"campo":"email","mensagem":"email tem que ter o formato [email protected]"}]}
--- erro em dois campos -> 400 ---
{"erro":"validacao falhou","quantidade":2,"campos":[{"campo":"nm_aluno","mensagem":"nm_aluno e obrigatorio e tem que ser texto nao vazio"},{"campo":"turma","mensagem":"turma tem que estar entre 1 e 12"}]}
--- corpo que e array -> 400 ---
{"erro":"o corpo tem que ser um objeto JSON"}
--- corpo vazio -> 400 ---
{"erro":"corpo vazio"}
--- 400 ou 422 ---
400 (RFC 7231): pedido malformado — o que o material usa
422 (RFC 4918): sintaxe ok, semantica errada
os dois servem; 400 e o que os frameworks e os clientes ja esperam
--- 500: quando o erro NAO e do cliente ---
se a consulta ao banco estoura, nao e 400: e 500, e a mensagem
tecnica vai para o LOG, nao para o cliente. O 500 nunca carrega
detalhe de SQL: quem le o corpo da resposta pode ser o proprio atacante.
--- a mensagem de erro tambem e API ---
"nm_aluno e obrigatorio e tem que ser texto nao vazio" -> o cliente sabe o que fazer
"TypeError: Cannot read properties of undefined" -> o cliente so trava
servidor encerrado com close().