Douglas Legramante

Douglas Legramante

Informática

04 — CRUD Completo


Sumário

  1. A arquitetura completa
  2. Create
  3. Read
  4. Update
  5. Delete
  6. Validando a entrada
  7. Tratando erros: 404 e erro central
  8. Juntando tudo: as rotas
  9. Status codes

1. A arquitetura completa

O mesmo padrão de Back-end I, agora com SQL de verdade e uma peça nova — a validação vira um middleware próprio, que roda antes do controller:

routes      → define a URL e o verbo HTTP
validação   → confere os dados antes de agir (novo!)
controller  → monta a resposta
service     → conversa com o banco (SQL)

Se os dados estiverem errados, a requisição nem chega no controller — o middleware de validação já responde com 400.

Nos controllers, o service inteiro é importado com um apelido curto, para deixar o código mais enxuto:

import * as service from '../services/produtosService.js';

2. Create

services/produtosService.js

export async function criar(produto) {
  const { nome, preco, categoria_id } = produto;

  const [r] = await pool.query(
    'INSERT INTO produtos (nome, preco, categoria_id) ' +
    'VALUES (?, ?, ?)',
    [nome, preco, categoria_id]
  );

  return r.insertId;
}

> Os ? no lugar de cada valor evitam SQL Injection — nunca concatene valores direto na string da query.

controllers/produtosController.js

export async function criar(req, res, next) {
  try {
    const id = await service.criar(req.body);
    res.status(201).json({ id, ...req.body });
  } catch (erro) {
    next(erro);
  }
}

Repare que não há validação aqui — isso já foi resolvido antes, pelo middleware validarProduto (seção 6). E o catch não responde 500 diretamente: ele chama next(erro), passando a bola para o error handler central (seção 7).


3. Read

Listar todos

// service
export async function listarTodos() {
  const [rows] = await pool.query('SELECT * FROM produtos');
  return rows;
}

// controller
export async function listar(req, res, next) {
  try {
    const produtos = await service.listarTodos();
    res.json(produtos);
  } catch (erro) {
    next(erro);
  }
}

Buscar por id

// service
export async function buscarPorId(id) {
  const [rows] = await pool.query(
    'SELECT * FROM produtos WHERE id = ?', [id]
  );
  return rows[0];
}

// controller
export async function buscarPorId(req, res, next) {
  try {
    const { id } = req.params;
    const produto = await service.buscarPorId(id);
    if (!produto) {
      return res.status(404).json({ erro: 'sem registro' });
    }
    res.json(produto);
  } catch (erro) {
    next(erro);
  }
}

4. Update

// service
export async function atualizar(id, produto) {
  const { nome, preco, categoria_id } = produto;
  const [r] = await pool.query(
    'UPDATE produtos SET nome=?, preco=?, ' +
    'categoria_id=? WHERE id=?',
    [nome, preco, categoria_id, id]
  );
  return r.affectedRows;
}

// controller
export async function atualizar(req, res, next) {
  try {
    const { id } = req.params;
    const existente = await service.buscarPorId(id);
    if (!existente) {
      return res.status(404).json({ erro: 'sem registro' });
    }
    await service.atualizar(id, req.body);
    res.json({ id, ...req.body });
  } catch (erro) {
    next(erro);
  }
}

> Por que checar buscarPorId antes, em vez de olhar affectedRows? Se o UPDATE não muda nenhum valor (os dados enviados são iguais aos já salvos), o MySQL retorna affectedRows = 0 mesmo com o registro existindo — isso daria um 404 incorreto. Buscando o registro primeiro, o 404 só acontece quando ele realmente não existe.


5. Delete

// service
export async function deletar(id) {
  const [r] = await pool.query(
    'DELETE FROM produtos WHERE id = ?', [id]
  );
  return r.affectedRows;
}

// controller
export async function deletar(req, res, next) {
  try {
    const { id } = req.params;
    const n = await service.deletar(id);
    if (n === 0) {
      return res.status(404).json({ erro: 'sem registro' });
    }
    res.status(204).send();
  } catch (erro) {
    next(erro);
  }
}

> Aqui o affectedRows continua confiável: um DELETE ou apaga uma linha, ou não apaga nenhuma — sem meio-termo ambíguo, ao contrário do UPDATE.


6. Validando a entrada

Um middleware roda antes do controller e decide se a requisição continua (next()) ou é interrompida com um erro:

// middlewares/validarProduto.js
export function validarProduto(req, res, next) {
  const { nome, preco } = req.body;
  const erros = [];

  if (!nome) erros.push('nome e obrigatorio');
  if (!preco || preco <= 0) erros.push('preco invalido');

  if (erros.length > 0) {
    return res.status(400).json({ erros });
  }
  next();
}

No projeto completo, vale validar também quantidade_estoque e categoria_id — o princípio é o mesmo: checar tipo e presença de cada campo, acumular mensagens de erro, e só chamar next() se estiver tudo certo.


7. Tratando erros: 404 e erro central

Diferente da validação, essas duas peças não ganham arquivo próprio — ficam definidas direto no index.js, depois de todas as rotas, do mesmo jeito que vocês já fizeram em Back-end I:

// index.js, depois de app.use('/api', produtosRoutes)

app.use((req, res) => {
  res.status(404).json({ erro: 'rota nao encontrada' });
});

app.use((erro, req, res, next) => {
  console.error(erro);
  res.status(500).json({ erro: 'erro interno' });
});
  • A primeira função só é alcançada se nenhuma rota cadastrada bateu com a requisição — por isso vem depois de todas as rotas.
  • A segunda função vira um error handler porque tem exatamente 4 parâmetros (erro, req, res, next) — é assim que o Express reconhece que essa função trata erros, e não é mais uma rota normal. Sempre que um controller chama next(erro), a execução pula direto para cá.

> Por que validarProduto é arquivo separado e essas duas não são? Porque validarProduto precisa ser reaproveitado em mais de uma rota (POST e PUT) — vale a pena isolar. Já o 404 e o error handler são usados uma única vez cada, no fim do index.js; criar um arquivo só para isso seria mais uma camada sem ganho real.


8. Juntando tudo: as rotas

// routes/produtosRoutes.js
import express from 'express';
import * as controller from '../controllers/produtosController.js';
import { validarProduto } from '../middlewares/validarProduto.js';

const router = express.Router();

router.post('/produtos', validarProduto, controller.criar);
router.get('/produtos', controller.listar);
router.get('/produtos/:id', controller.buscarPorId);
router.put('/produtos/:id', validarProduto, controller.atualizar);
router.delete('/produtos/:id', controller.deletar);

export default router;
// index.js
app.use('/api', produtosRoutes);

// a partir daqui, só entra quem não encontrou uma rota válida acima —
// e por último, o error handler, que só é alcançado via next(erro)
app.use((req, res) => { /* 404 — código completo na seção 7 */ });
app.use((erro, req, res, next) => { /* erro central — código completo na seção 7 */ });

O middleware de validação entra entre a rota e o controller — só nas rotas que recebem dados (POST e PUT).


9. Status codes

CódigoNomeQuando usar
200OKSucesso — leitura ou atualização
201CreatedRecurso criado com sucesso
204No ContentSucesso, sem corpo de resposta (delete)
400Bad RequestDados inválidos ou faltando (pego pelo middleware de validação)
404Not FoundRegistro não encontrado, ou rota inexistente
500Server ErrorErro inesperado — sempre tratado pelo erro central do index.js