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.