Introdução

Neste guia passo a passo, vamos implementar um CRUD (Create, Read, Update, Delete) básico para a entidade `produto` do Sistema de Vendas, utilizando Node.js, Express e PostgreSQL. Seguiremos a estrutura definida no `script_vendas.sql`.

Vamos focar na implementação do backend, criando uma API RESTful que permitirá:

  • Listar todos os produtos
  • Obter detalhes de um produto específico pelo código
  • Cadastrar novos produtos
  • Atualizar produtos existentes
  • Remover produtos

Este guia assume que você já configurou o ambiente com Node.js, npm e PostgreSQL instalados e que o banco de dados foi criado e populado com o `script_vendas.sql`. Consulte os módulos anteriores se necessário.

Passo 1: Configuração Inicial do Projeto

1.1. Estrutura e Inicialização

Se ainda não o fez, crie a estrutura do projeto e inicialize o npm:

mkdir sistema-vendas-pg
cd sistema-vendas-pg
npm init -y
mkdir -p src/config src/controllers src/models src/routes src/utils

1.2. Instalar Dependências

Instale as dependências necessárias para Express e PostgreSQL:

npm install express pg dotenv cors
npm install --save-dev nodemon
  • express: Framework web.
  • pg: Driver Node.js para PostgreSQL.
  • dotenv: Carregar variáveis de ambiente.
  • cors: Habilitar CORS.
  • nodemon: Reiniciar servidor automaticamente (desenvolvimento).

1.3. Configurar `package.json` e `.gitignore`

Adicione os scripts `start` e `dev` ao `package.json` e crie um `.gitignore` para ignorar `node_modules/` e `.env`.

// package.json (scripts section)
"scripts": {
  "start": "node server.js",
  "dev": "nodemon server.js"
},
# .gitignore
node_modules/
.env

1.4. Criar Arquivo `.env`

Crie o arquivo `.env` na raiz com as credenciais do seu banco PostgreSQL:

# .env
PORT=3000
DB_USER=seu_usuario_pg
DB_HOST=localhost
DB_DATABASE=seu_banco_pg
DB_PASSWORD=sua_senha_pg
DB_PORT=5432

Importante: Certifique-se de que o banco de dados (`DB_DATABASE`) já existe e que o `script_vendas.sql` foi executado nele.

Passo 2: Configuração da Conexão PostgreSQL

Vamos configurar um pool de conexões com o PostgreSQL.

// src/config/database.js
const { Pool } = require("pg");
require("dotenv").config();

const pool = new Pool({
  user: process.env.DB_USER,
  host: process.env.DB_HOST,
  database: process.env.DB_DATABASE,
  password: process.env.DB_PASSWORD,
  port: process.env.DB_PORT,
});

// Teste de conexão (opcional)
pool.query("SELECT NOW()", (err, res) => {
  if (err) {
    console.error("Erro ao conectar ao PostgreSQL:", err);
  } else {
    console.log("Conexão com PostgreSQL estabelecida.");
  }
});

module.exports = pool;

Passo 3: Implementação do Model (`produtoModel.js`)

Criaremos o model para interagir com a tabela `produto` no PostgreSQL.

// src/models/produtoModel.js
const pool = require("../config/database");

class ProdutoModel {
  static async listarTodos() {
    const client = await pool.connect();
    try {
      const res = await client.query("SELECT * FROM produto ORDER BY descricao_produto");
      return res.rows;
    } finally {
      client.release();
    }
  }

  static async buscarPorCodigo(codigo) {
    const client = await pool.connect();
    try {
      const res = await client.query("SELECT * FROM produto WHERE codigo_produto = $1", [codigo]);
      return res.rows.length > 0 ? res.rows[0] : null;
    } finally {
      client.release();
    }
  }

  static async criar(produto) {
    const { codigo_produto, unidade, descricao_produto, val_unit } = produto;
    const client = await pool.connect();
    try {
      const res = await client.query(
        "INSERT INTO produto (codigo_produto, unidade, descricao_produto, val_unit) VALUES ($1, $2, $3, $4) RETURNING *",
        [codigo_produto, unidade, descricao_produto, val_unit]
      );
      return res.rows[0];
    } finally {
      client.release();
    }
  }

  static async atualizar(codigo, produto) {
    const { unidade, descricao_produto, val_unit } = produto;
    const client = await pool.connect();
    try {
      const res = await client.query(
        "UPDATE produto SET unidade = $1, descricao_produto = $2, val_unit = $3 WHERE codigo_produto = $4 RETURNING *",
        [unidade, descricao_produto, val_unit, codigo]
      );
      return res.rows.length > 0 ? res.rows[0] : null;
    } finally {
      client.release();
    }
  }

  static async excluir(codigo) {
    const client = await pool.connect();
    try {
      // Verificar dependências em item_pedido antes de excluir (IMPORTANTE!)
      const depRes = await client.query("SELECT 1 FROM item_pedido WHERE codigo_produto = $1 LIMIT 1", [codigo]);
      if (depRes.rows.length > 0) {
        throw new Error("Não é possível excluir o produto pois ele existe em um ou mais pedidos.");
      }
      
      const res = await client.query("DELETE FROM produto WHERE codigo_produto = $1 RETURNING *", [codigo]);
      return res.rows.length > 0;
    } finally {
      client.release();
    }
  }
}

module.exports = ProdutoModel;

Observação: A função `excluir` inclui uma verificação básica de dependência na tabela `item_pedido`. Em um sistema real, tratamentos de erro e validações mais robustas seriam necessários.

Passo 4: Implementação do Controller (`produtoController.js`)

Criaremos o controller para lidar com a lógica das requisições para produtos.

// src/controllers/produtoController.js
const ProdutoModel = require("../models/produtoModel");

class ProdutoController {
  static async listar(req, res) {
    try {
      const produtos = await ProdutoModel.listarTodos();
      res.json(produtos);
    } catch (error) {
      console.error("Erro no controller ao listar produtos:", error);
      res.status(500).json({ message: "Erro interno ao buscar produtos", error: error.message });
    }
  }

  static async buscar(req, res) {
    const { codigo } = req.params;
    try {
      const produto = await ProdutoModel.buscarPorCodigo(parseInt(codigo)); // Garante que o código seja número
      if (produto) {
        res.json(produto);
      } else {
        res.status(404).json({ message: "Produto não encontrado" });
      }
    } catch (error) {
      console.error(`Erro no controller ao buscar produto ${codigo}:`, error);
      res.status(500).json({ message: "Erro interno ao buscar produto", error: error.message });
    }
  }

  static async criar(req, res) {
    const novoProduto = req.body;
    // Adicionar validações aqui (ex: verificar se campos obrigatórios existem)
    if (!novoProduto.codigo_produto || !novoProduto.descricao_produto || !novoProduto.val_unit) {
        return res.status(400).json({ message: "Código, descrição e valor unitário são obrigatórios." });
    }
    try {
      const produtoCriado = await ProdutoModel.criar(novoProduto);
      res.status(201).json(produtoCriado);
    } catch (error) {
      console.error("Erro no controller ao criar produto:", error);
      // Verificar erro de chave duplicada (código já existe)
      if (error.code === '23505') { // Código de erro do PostgreSQL para unique_violation
          return res.status(409).json({ message: "Código do produto já existe.", error: error.message });
      }
      res.status(500).json({ message: "Erro interno ao criar produto", error: error.message });
    }
  }

  static async atualizar(req, res) {
    const { codigo } = req.params;
    const dadosProduto = req.body;
    // Adicionar validações aqui
    try {
      const produtoAtualizado = await ProdutoModel.atualizar(parseInt(codigo), dadosProduto);
      if (produtoAtualizado) {
        res.json(produtoAtualizado);
      } else {
        res.status(404).json({ message: "Produto não encontrado para atualização" });
      }
    } catch (error) {
      console.error(`Erro no controller ao atualizar produto ${codigo}:`, error);
      res.status(500).json({ message: "Erro interno ao atualizar produto", error: error.message });
    }
  }

  static async excluir(req, res) {
    const { codigo } = req.params;
    try {
      const excluido = await ProdutoModel.excluir(parseInt(codigo));
      if (excluido) {
        res.status(200).json({ message: "Produto excluído com sucesso" }); // Ou 204 No Content
      } else {
        res.status(404).json({ message: "Produto não encontrado para exclusão" });
      }
    } catch (error) {
      console.error(`Erro no controller ao excluir produto ${codigo}:`, error);
      // Tratar erro específico de dependência
      if (error.message.includes("existe em um ou mais pedidos")) {
          return res.status(409).json({ message: error.message });
      }
      res.status(500).json({ message: "Erro interno ao excluir produto", error: error.message });
    }
  }
}

module.exports = ProdutoController;

Passo 5: Implementação das Rotas (`produtoRoutes.js`)

Definiremos as rotas da API para produtos.

// src/routes/produtoRoutes.js
const express = require("express");
const ProdutoController = require("../controllers/produtoController");

const router = express.Router();

router.get("/", ProdutoController.listar);
router.get("/:codigo", ProdutoController.buscar);
router.post("/", ProdutoController.criar);
router.put("/:codigo", ProdutoController.atualizar);
router.delete("/:codigo", ProdutoController.excluir);

module.exports = router;

Passo 6: Configuração do Servidor Express (`app.js` e `server.js`)

Configuraremos o aplicativo Express principal e o ponto de entrada do servidor.

// src/app.js
const express = require("express");
const cors = require("cors");
const produtoRoutes = require("./routes/produtoRoutes");
// Importar outras rotas (cliente, vendedor, pedido) aqui quando criadas

const app = express();

// Middlewares
app.use(cors()); // Habilita CORS para todas as origens (ajustar em produção)
app.use(express.json()); // Para parsear JSON no corpo das requisições

// Rotas
app.use("/api/produtos", produtoRoutes);
// app.use("/api/clientes", clienteRoutes);
// app.use("/api/vendedores", vendedorRoutes);
// app.use("/api/pedidos", pedidoRoutes);

// Rota raiz (opcional)
app.get("/", (req, res) => {
  res.send("API Sistema de Vendas - Backend Curso");
});

// Middleware de tratamento de erro básico (opcional, pode ser mais elaborado)
app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(500).send("Algo deu errado!");
});

module.exports = app;
// server.js (na raiz do projeto)
require("dotenv").config();
const app = require("./src/app");

const port = process.env.PORT || 3001; // Usa a porta do .env ou 3001 como padrão

app.listen(port, () => {
  console.log(`Servidor rodando na porta ${port}`);
});

Passo 7: Executando e Testando a API

Inicie o servidor em modo de desenvolvimento:

npm run dev

Agora você pode usar uma ferramenta como Postman, Insomnia ou `curl` para testar os endpoints da API:

  • Listar Produtos: `GET http://localhost:3000/api/produtos`
  • Buscar Produto 25: `GET http://localhost:3000/api/produtos/25`
  • Criar Produto: `POST http://localhost:3000/api/produtos` com JSON no corpo:
{
    "codigo_produto": 99,
    "unidade": "UN",
    "descricao_produto": "Produto Teste API",
    "val_unit": 15.75
}
  • Atualizar Produto 99: `PUT http://localhost:3000/api/produtos/99` com JSON no corpo:
{
    "unidade": "CX",
    "descricao_produto": "Produto Teste API Atualizado",
    "val_unit": 18.50
}
  • Excluir Produto 99: `DELETE http://localhost:3000/api/produtos/99`

Conclusão

Este guia demonstrou a implementação básica de um CRUD para a entidade `produto` usando Node.js, Express e PostgreSQL, seguindo o modelo do `script_vendas.sql`. A partir daqui, você pode expandir a API para incluir as outras entidades (cliente, vendedor, pedido, item_pedido), adicionar validações mais robustas, implementar autenticação e construir um frontend para interagir com o backend.

Próximos Passos

Explore a implementação das outras entidades ou reveja os conceitos nos módulos do curso.