Leodario.com

Leodario.com – Tudo sobre Tecnologia

Aula 46 – API JavaScript, Node.js e Express – Database Fundamentals – SQL vs NoSQL

Imagem destacada da aula de API

Introdução

Olá, futuros arquitetos de sistemas e desenvolvedores de APIs! Sejam muito bem-vindos à Aula 46 do nosso curso. Hoje, mergulharemos em um tópico que é verdadeiramente vital para qualquer API moderna: os fundamentos de banco de dados, com um foco especial na eterna questão: SQL ou NoSQL?

Imagine que você está construindo uma biblioteca colossal. Seus livros precisam ser organizados. Uma abordagem seria ter prateleiras com categorias fixas, um catálogo rigoroso, e cada livro tem seu lugar exato, com um número de identificação único que pode ser referenciado por outros livros (ex: “veja capítulo 3 do livro X”). Esta é a essência do mundo SQL, onde a estrutura e as relações são primordiais.

Agora, e se você estivesse organizando uma enorme coleção de tesouros descobertos de diversas expedições? Alguns tesouros são caixas com joias soltas, outros são pergaminhos com anotações irregulares, e há até mesmo artefatos com descrições mutáveis. Você simplesmente guarda cada item em um compartimento, com uma etiqueta simples, sem se preocupar em encaixá-lo numa estrutura pré-definida de prateleira e categoria. A flexibilidade é a chave. Esta é uma forma de visualizar o universo NoSQL.

Por que essa compreensão é tão essencial para APIs modernas? Porque a escolha do seu banco de dados é a espinha dorsal da sua aplicação. Ela vai influenciar diretamente a performance, a escalabilidade, a resiliência e a flexibilidade da sua API. Uma decisão inadequada pode levar a gargalos significativos ou a uma reengenharia custosa no futuro.

Nesta aula, você vai compreender exatamente as diferenças fundamentais entre bancos de dados SQL e NoSQL, aprenderá quando cada um é a escolha mais apropriada e verá como essa decisão se integra ao ecossistema Node.js/Express. Prepare-se para tomar decisões mais inteligentes e projetar APIs mais robustas!

Conceito Fundamental

A distinção entre SQL e NoSQL representa duas filosofias profundamente diferentes de armazenamento e recuperação de dados. Vamos desvendar cada uma delas.

Bancos de Dados SQL (Relacionais)

Os bancos de dados SQL, que significa Structured Query Language (Linguagem de Consulta Estruturada), são o tipo mais tradicional e amplamente utilizado. Eles se baseiam no modelo relacional, onde os dados são organizados em tabelas. Cada tabela possui linhas (registros) e colunas (atributos).

    • Estrutura e Schema: A característica mais marcante é o schema fixo e rigoroso. Isso significa que você deve definir a estrutura das suas tabelas (nome das colunas, tipo de dados, restrições) antes mesmo de inserir qualquer informação. Essa rigidez garante alta integridade de dados.
    • Relações: Dados em diferentes tabelas podem ser interligados através de chaves primárias (identificador único de uma linha) e chaves estrangeiras (uma chave primária de outra tabela). Isso viabiliza a criação de relações complexas e a execução de operações de JOIN para combinar informações de múltiplas tabelas em uma única consulta.
    • ACID: Os bancos SQL são projetados para aderir aos princípios ACID:
      • Atomicidade: Uma transação é “tudo ou nada”. Ou todas as operações são concluídas com sucesso, ou nenhuma delas é aplicada.
      • Consistência: Os dados no banco permanecem válidos e seguem as regras de integridade após uma transação.
      • Isolamento: Transações simultâneas são executadas de forma isolada, como se fossem sequenciais, evitando interferências.
      • Durabilidade: Uma vez que uma transação é confirmada, suas alterações são permanentes, mesmo em caso de falha do sistema.
    • Exemplos: MySQL, PostgreSQL, Oracle Database, SQL Server.
    • Casos de Uso: São a escolha predileta para sistemas que exigem alta consistência e integridade, como transações financeiras, sistemas de gestão de estoque, ERP (Enterprise Resource Planning), e aplicações com dados bem estruturados e relações complexas.
    • Vantagens: Maturidade, ampla comunidade, suporte a consultas complexas, garantia de integridade transacional.
    • Desvantagens: Escalabilidade vertical (tipicamente, exigindo máquinas mais potentes), rigidez do schema que pode dificultar mudanças rápidas.

Bancos de Dados NoSQL (Não Relacionais)

O termo NoSQL, que significa Not only SQL (Não apenas SQL), surgiu para categorizar uma nova geração de bancos de dados que não aderem ao modelo relacional tradicional. Eles foram desenvolvidos para lidar com os desafios da era do Big Data, da web e das aplicações em tempo real.

    • Estrutura e Schema: Diferente do SQL, os bancos NoSQL são caracterizados por um schema flexível ou totalmente schemaless. Isso significa que você não precisa definir a estrutura dos seus dados antecipadamente, podendo armazenar documentos com diferentes campos na mesma coleção. Essa flexibilidade é um grande atrativo para o desenvolvimento ágil.
    • Tipos Variados: Não existe um único “tipo” de NoSQL. Eles são categorizados por como armazenam os dados:
      • Documento: Armazenam dados como documentos auto-contidos (JSON, BSON, XML). Ex: MongoDB, Couchbase.
      • Chave-Valor: Armazenam pares de chave-valor simples. Ex: Redis, DynamoDB.
      • Colunar: Armazenam dados em colunas em vez de linhas, otimizados para agregação de grandes volumes de dados. Ex: Apache Cassandra, HBase.
      • Grafo: Armazenam dados como nós e arestas, ideais para representar relações complexas e interconectadas. Ex: Neo4j.
    • BASE: Muitos bancos NoSQL priorizam a filosofia BASE em detrimento do ACID (especialmente para consistência forte):
      • Basically Available: O sistema continua funcionando e respondendo a requisições, mesmo com falhas.
      • Soft State: O estado do sistema pode mudar com o tempo, mesmo sem entrada.
      • Eventually Consistent: Após um período, todos os dados replicados se tornarão consistentes.
    • Exemplos: MongoDB, Redis, Cassandra, Neo4j.
    • Casos de Uso: Excelentes para big data, aplicações de conteúdo web (blogs, redes sociais), IoT (Internet das Coisas), dados em tempo real, catálogos de produtos e cenários onde a escalabilidade horizontal e a flexibilidade são mais importantes que a consistência transacional rigorosa.
    • Vantagens: Escalabilidade horizontal (fácil de distribuir em múltiplos servidores), flexibilidade do schema, performance para grandes volumes de dados não estruturados.
    • Desvantagens: Menor maturidade em alguns casos, consultas complexas podem ser mais desafiadoras, ausência de joins tradicionais, menor garantia de integridade transacional por padrão.

A integração de ambos os tipos de bancos de dados com Node.js/Express é realizada através de drivers ou ORMs/ODMs (Object-Relational Mappers / Object-Document Mappers) específicos. Para SQL, usamos pacotes como mysql2 ou pg, muitas vezes com ORMs como Sequelize ou TypeORM. Para NoSQL, como MongoDB, utilizamos o mongoose, que é um ODM robusto.

Implementação Prática

Para ilustrar a integração e a diferença conceitual, vamos desenvolver um pequeno servidor Express. Ele não terá um banco de dados real em execução para ambos os tipos (pois a configuração completa de dois DBs em 10 minutos é inviável), mas vamos simular as interações para que você compreenda como os drivers e os modelos de dados seriam estruturados em um projeto Node.js/Express real. Vou focar na estrutura do código e nos padrões de integração.

Para este exemplo, assumiremos que no seu HostGator Plano M você tem acesso a um banco de dados MySQL (SQL) e que, para MongoDB (NoSQL), você usaria um serviço externo como o MongoDB Atlas, que é a prática comum e viável com Node.js em hospedagens compartilhadas.

Primeiro, crie um novo projeto:

mkdir db-fundamentals-api
cd db-fundamentals-api
npm init -y
npm install express dotenv mysql2 mongoose winston

Vamos criar a estrutura de pastas:

mkdir config models routes utils
touch app.js .env config/db.js utils/logger.js models/user.model.js models/product.model.js routes/data.routes.js

1. Configuração do Ambiente e Logger

Crie um arquivo .env para as variáveis de ambiente:

DB_HOST_SQL=localhost
DB_USER_SQL=root
DB_PASSWORD_SQL=sua_senha_mysql
DB_NAME_SQL=meu_banco_sql
DB_PORT_SQL=3306

MONGODB_URI_NOSQL=mongodb://localhost:27017/meu_banco_nosql # Ou um URI do MongoDB Atlas

Adicione um logger básico em utils/logger.js usando winston, uma biblioteca profissional:

// utils/logger.js
const winston = require('winston');

const logger = winston.createLogger({ level: 'info', format: winston.format.combine( winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), winston.format.errors({ stack: true }), winston.format.splat(), winston.format.json() ), transports: [ new winston.transports.Console({ format: winston.format.combine( winston.format.colorize(), winston.format.simple() ) }), // Em produção, você adicionaria transportes para arquivos ou serviços de log // new winston.transports.File({ filename: 'error.log', level: 'error' }), // new winston.transports.File({ filename: 'combined.log' }) ], exitOnError: false, // Não sair em exceções não tratadas });

// Se não estiver em ambiente de produção, logar também no console para info/debug if (process.env.NODE_ENV !== 'production') { logger.add(new winston.transports.Console({ format: winston.format.simple() })); }

module.exports = logger;

2. Conexão com Bancos de Dados (Conceitual)

Em config/db.js, configuraremos as conexões. Note que a conexão NoSQL (MongoDB) usa um driver diferente da SQL (MySQL).

// config/db.js
const mysql = require('mysql2/promise'); // Driver SQL para MySQL
const mongoose = require('mongoose');    // ODM NoSQL para MongoDB
const logger = require('../utils/logger');
require('dotenv').config();

// Configuração MySQL (SQL) const sqlConfig = { host: process.env.DB_HOST_SQL, user: process.env.DB_USER_SQL, password: process.env.DB_PASSWORD_SQL, database: process.env.DB_NAME_SQL, port: process.env.DB_PORT_SQL, waitForConnections: true, connectionLimit: 10, queueLimit: 0 };

let sqlPool;

const connectSQL = async () => { try { sqlPool = mysql.createPool(sqlConfig); await sqlPool.getConnection(); // Testa a conexão logger.info('Conexão SQL (MySQL) estabelecida com sucesso!'); } catch (error) { logger.error('Falha ao conectar ao banco de dados SQL (MySQL):', error.message); process.exit(1); // Encerra a aplicação se a conexão falhar } };

const getSQLConnection = () => { if (!sqlPool) { logger.error('Pool de conexão SQL não inicializado.'); throw new Error('Pool de conexão SQL não inicializado.'); } return sqlPool; };

// Configuração MongoDB (NoSQL) const connectNoSQL = async () => { try { await mongoose.connect(process.env.MONGODB_URI_NOSQL, { useNewUrlParser: true, useUnifiedTopology: true, // useCreateIndex: true, // Deprecado no Mongoose 6+ // useFindAndModify: false // Deprecado no Mongoose 6+ }); logger.info('Conexão NoSQL (MongoDB) estabelecida com sucesso!'); } catch (error) { logger.error('Falha ao conectar ao banco de dados NoSQL (MongoDB):', error.message); process.exit(1); // Encerra a aplicação se a conexão falhar } };

module.exports = { connectSQL, getSQLConnection, connectNoSQL };

3. Modelos de Dados

Aqui é onde a diferença de modelagem se torna mais evidente.

Modelo SQL (models/user.model.js): Usaremos funções que executam comandos SQL diretamente, como faríamos com o driver mysql2. Em um projeto real, você poderia usar um ORM como Sequelize para uma abstração maior.

// models/user.model.js
const { getSQLConnection } = require('../config/db');
const logger = require('../utils/logger');

class User { static async createTable() { // Exemplo de criação de tabela - em produção, migrations seriam usadas const pool = getSQLConnection(); const query = CREATE TABLE IF NOT EXISTS users ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255) NOT NULL, email VARCHAR(255) UNIQUE NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB; ; try { await pool.execute(query); logger.info('Tabela users verificada/criada com sucesso (SQL).'); } catch (error) { logger.error('Erro ao criar tabela users (SQL):', error.message); throw error; } }

static async createUser(name, email) { const pool = getSQLConnection(); const query = 'INSERT INTO users (name, email) VALUES (?, ?)'; try { const [result] = await pool.execute(query, [name, email]); logger.info('Novo usuário SQL criado com ID:', result.insertId); return { id: result.insertId, name, email }; } catch (error) { logger.error('Erro ao criar usuário SQL:', error.message); throw error; } }

static async getAllUsers() { const pool = getSQLConnection(); const query = 'SELECT id, name, email FROM users'; try { const [rows] = await pool.execute(query); logger.info('Usuários SQL recuperados:', rows.length); return rows; } catch (error) { logger.error('Erro ao recuperar usuários SQL:', error.message); throw error; } }

static async getUserById(id) { const pool = getSQLConnection(); const query = 'SELECT id, name, email FROM users WHERE id = ?'; try { const [rows] = await pool.execute(query, [id]); logger.info('Usuário SQL recuperado por ID:', id); return rows[0]; // Retorna o primeiro usuário encontrado } catch (error) { logger.error('Erro ao recuperar usuário SQL por ID:', error.message); throw error; } } }

module.exports = User;

Modelo NoSQL (models/product.model.js): Usaremos Mongoose para definir um schema e interagir com o MongoDB. Isso demonstra a flexibilidade e a forma orientada a objetos do NoSQL de documentos.

// models/product.model.js
const mongoose = require('mongoose');
const logger = require('../utils/logger');

// Definição do Schema para um Produto const productSchema = new mongoose.Schema({ name: { type: String, required: true, trim: true }, description: { type: String, required: false, trim: true }, price: { type: Number, required: true, min: 0 }, category: { type: String, required: true, trim: true }, stock: { type: Number, default: 0, min: 0 }, createdAt: { type: Date, default: Date.now } });

// Métodos estáticos (ou de instância) para interações comuns productSchema.statics.findProductsByCategory = async function (category) { try { const products = await this.find({ category: category }); logger.info('Produtos NoSQL recuperados por categoria:', category); return products; } catch (error) { logger.error('Erro ao recuperar produtos NoSQL por categoria:', error.message); throw error; } };

// Cria o Modelo a partir do Schema const Product = mongoose.model('Product', productSchema);

module.exports = Product;

4. Rotas da API

Em routes/data.routes.js, criaremos as rotas que usarão nossos modelos.

// routes/data.routes.js
const express = require('express');
const router = express.Router();
const User = require('../models/user.model');
const Product = require('../models/product.model');
const logger = require('../utils/logger');

// Rota para criar um novo usuário (SQL) router.post('/sql/users', async (req, res) => { const { name, email } = req.body; if (!name || !email) { logger.warn('Validação de entrada falhou para /sql/users: name ou email ausente.'); return res.status(400).json({ message: 'Nome e email são obrigatórios.' }); } try { const newUser = await User.createUser(name, email); res.status(201).json({ message: 'Usuário SQL criado com sucesso!', user: newUser }); } catch (error) { logger.error('Erro na rota POST /sql/users:', error.message); res.status(500).json({ message: 'Erro ao criar usuário SQL.', error: error.message }); } });

// Rota para listar todos os usuários (SQL) router.get('/sql/users', async (req, res) => { try { const users = await User.getAllUsers(); res.status(200).json({ message: 'Usuários SQL recuperados com sucesso!', users }); } catch (error) { logger.error('Erro na rota GET /sql/users:', error.message); res.status(500).json({ message: 'Erro ao recuperar usuários SQL.', error: error.message }); } });

// Rota para obter um usuário por ID (SQL) router.get('/sql/users/:id', async (req, res) => { const { id } = req.params; if (isNaN(id)) { logger.warn('Validação de entrada falhou para /sql/users/:id: ID inválido.'); return res.status(400).json({ message: 'ID de usuário deve ser um número válido.' }); } try { const user = await User.getUserById(id); if (user) { res.status(200).json({ message: 'Usuário SQL encontrado.', user }); } else { res.status(404).json({ message: 'Usuário SQL não encontrado.' }); } } catch (error) { logger.error('Erro na rota GET /sql/users/:id:', error.message); res.status(500).json({ message: 'Erro ao recuperar usuário SQL.', error: error.message }); } });

// Rota para criar um novo produto (NoSQL) router.post('/nosql/products', async (req, res) => { const { name, description, price, category, stock } = req.body; if (!name || !price || !category) { logger.warn('Validação de entrada falhou para /nosql/products: name, price ou category ausente.'); return res.status(400).json({ message: 'Nome, preço e categoria são obrigatórios.' }); } try { const newProduct = new Product({ name, description, price, category, stock }); await newProduct.save(); // Salva no MongoDB res.status(201).json({ message: 'Produto NoSQL criado com sucesso!', product: newProduct }); } catch (error) { logger.error('Erro na rota POST /nosql/products:', error.message); res.status(500).json({ message: 'Erro ao criar produto NoSQL.', error: error.message }); } });

// Rota para listar todos os produtos (NoSQL) router.get('/nosql/products', async (req, res) => { try { const products = await Product.find({}); // Busca todos os produtos res.status(200).json({ message: 'Produtos NoSQL recuperados com sucesso!', products }); } catch (error) { logger.error('Erro na rota GET /nosql/products:', error.message); res.status(500).json({ message: 'Erro ao recuperar produtos NoSQL.', error: error.message }); } });

// Rota para listar produtos por categoria (NoSQL - usando método estático) router.get('/nosql/products/category/:category', async (req, res) => { const { category } = req.params; try { const products = await Product.findProductsByCategory(category); res.status(200).json({ message: Produtos NoSQL da categoria '${category}' recuperados., products }); } catch (error) { logger.error('Erro na rota GET /nosql/products/category/:category:', error.message); res.status(500).json({ message: 'Erro ao recuperar produtos NoSQL por categoria.', error: error.message }); } });

module.exports = router;

5. Aplicação Principal (app.js)

Conecte tudo em app.js.

// app.js
const express = require('express');
const { connectSQL, connectNoSQL } = require('./config/db');
const dataRoutes = require('./routes/data.routes');
const User = require('./models/user.model'); // Importar para criar a tabela
const logger = require('./utils/logger');
require('dotenv').config();

const app = express(); const PORT = process.env.PORT || 3000;

// Middlewares app.use(express.json()); // Habilita o parsing de JSON no corpo das requisições

// Conexão com os bancos de dados const startServer = async () => { try { await connectSQL(); // Tenta conectar ao MySQL await User.createTable(); // Garante que a tabela 'users' existe no MySQL

await connectNoSQL(); // Tenta conectar ao MongoDB

// Rotas da API app.use('/api', dataRoutes);

// Rota padrão para teste app.get('/', (req, res) => { res.send('API de Fundamentos de Banco de Dados está rodando!'); });

// Middleware de tratamento de erros global app.use((err, req, res, next) => { logger.error('Erro não tratado na API:', err.stack); res.status(500).send('Algo deu muito errado!'); });

app.listen(PORT, () => { logger.info(Servidor rodando na porta ${PORT}); logger.info(Acesse: http://localhost:${PORT}); });

} catch (error) { logger.error('Falha ao iniciar o servidor:', error.message); process.exit(1); // Encerra a aplicação em caso de falha crítica } };

startServer();

Para rodar este código:

    • Certifique-se de ter um servidor MySQL rodando localmente (ou acessível via DB_HOST_SQL). Crie um banco de dados chamado meu_banco_sql e um usuário com as credenciais configuradas no .env.
    • Para MongoDB, você pode instalar o MongoDB localmente ou usar um cluster gratuito no MongoDB Atlas e atualizar MONGODB_URI_NOSQL.
    • Execute node app.js no terminal.

Este exemplo prático demonstra a diferença nas abordagens de modelagem e interação com bancos de dados SQL e NoSQL usando drivers e ORMs/ODMs padrão do Node.js/Express. As melhores práticas incluem o uso de variáveis de ambiente para credenciais, logging robusto com Winston e tratamento de erros adequado para uma experiência enterprise.

Configurações Específicas para HostGator Plano M:

    • Para MySQL, o HostGator Plano M geralmente oferece acesso via localhost (se sua aplicação Node.js estiver no mesmo servidor) ou via um host específico fornecido pelo cPanel. Certifique-se de usar as credenciais corretas criadas no seu cPanel para o banco de dados MySQL.
    • Para MongoDB, o HostGator Plano M geralmente não oferece suporte nativo. A abordagem recomendada é utilizar um serviço de MongoDB gerenciado externamente, como o MongoDB Atlas (que oferece um plano gratuito). Você configuraria o MONGODB_URI_NOSQL com a string de conexão fornecida pelo Atlas.
    • A Node.js em HostGator Plano M geralmente requer configurações específicas para o arquivo package.json (campo "start") e pode precisar de um arquivo .htaccess para direcionar as requisições para seu aplicativo Node.js. Consulte a documentação da HostGator para o “Setup Node.js App”.

Exercício Hands-On

Agora é a sua vez de colocar a mão na massa e aprofundar sua compreensão! Um dos pilares de qualquer API é a capacidade de atualizar informações existentes.

Desafio Prático: Implemente Rotas de Atualização

Adicione duas novas rotas à sua API, uma para cada tipo de banco de dados, que permitam a atualização de um registro existente:

    • SQL (Usuários): Crie uma rota PUT /api/sql/users/:id que receba no corpo da requisição campos como name e email. Ela deve encontrar o usuário pelo id fornecido nos parâmetros da URL e atualizar seus dados no banco MySQL.
    • NoSQL (Produtos): Crie uma rota PUT /api/nosql/products/:id que receba no corpo da requisição campos como name, description, price, category, stock. Ela deve encontrar o produto pelo id (neste caso, o _id gerado pelo MongoDB) e atualizar suas informações.

Lembre-se de incluir:

    • Validação básica dos dados de entrada.
    • Tratamento de erros robusto.
    • Uso do logger para registrar as operações.
    • Retorno de uma resposta JSON apropriada (sucesso, não encontrado, erro).

Solução Detalhada Passo a Passo

Passo 1: Atualizar o Modelo SQL (models/user.model.js)

Adicione um método estático updateUser à classe User:

// models/user.model.js (adicionar dentro da classe User)
// ... código existente ...

static async updateUser(id, name, email) { const pool = getSQLConnection(); const query = 'UPDATE users SET name = ?, email = ? WHERE id = ?'; try { const [result] = await pool.execute(query, [name, email, id]); if (result.affectedRows === 0) { logger.warn('Nenhum usuário SQL encontrado para atualização com ID:', id); return null; // Usuário não encontrado } logger.info('Usuário SQL atualizado com ID:', id); return { id, name, email }; } catch (error) { logger.error('Erro ao atualizar usuário SQL:', error.message); throw error; } } // ...

Passo 2: Atualizar o Modelo NoSQL (models/product.model.js)

Não precisamos adicionar um método estático, pois Mongoose já oferece métodos como findByIdAndUpdate ou findOneAndUpdate. Vamos usá-lo diretamente na rota.

Passo 3: Adicionar Rotas em (routes/data.routes.js)

Adicione as novas rotas PUT:

// routes/data.routes.js (adicionar após as rotas existentes)
// ... código existente ...

// Rota para atualizar um usuário (SQL) router.put('/sql/users/:id', async (req, res) => { const { id } = req.params; const { name, email } = req.body;

if (isNaN(id)) { logger.warn('Validação de entrada falhou para PUT /sql/users/:id: ID inválido.'); return res.status(400).json({ message: 'ID de usuário deve ser um número válido.' }); } if (!name && !email) { // Pelo menos um campo para atualizar logger.warn('Validação de entrada falhou para PUT /sql/users/:id: Nenhum campo para atualizar fornecido.'); return res.status(400).json({ message: 'Forneça pelo menos nome ou email para atualização.' }); }

try { const updatedUser = await User.updateUser(id, name, email); if (updatedUser) { res.status(200).json({ message: 'Usuário SQL atualizado com sucesso!', user: updatedUser }); } else { res.status(404).json({ message: 'Usuário SQL não encontrado para atualização.' }); } } catch (error) { logger.error('Erro na rota PUT /sql/users/:id:', error.message); res.status(500).json({ message: 'Erro ao atualizar usuário SQL.', error: error.message }); } });

// Rota para atualizar um produto (NoSQL) router.put('/nosql/products/:id', async (req, res) => { const { id } = req.params; // _id do MongoDB const updates = req.body; // Objeto com os campos a serem atualizados

// Validação básica: verificar se há campos para atualizar if (Object.keys(updates).length === 0) { logger.warn('Validação de entrada falhou para PUT /nosql/products/:id: Nenhum campo para atualizar fornecido.'); return res.status(400).json({ message: 'Forneça pelo menos um campo para atualização.' }); }

try { // Mongoose findByIdAndUpdate: // - id: o _id do documento // - updates: o objeto com os campos a serem atualizados // - { new: true }: retorna o documento atualizado // - { runValidators: true }: executa as validações do schema antes de atualizar const updatedProduct = await Product.findByIdAndUpdate(id, updates, { new: true, runValidators: true });

if (updatedProduct) { res.status(200).json({ message: 'Produto NoSQL atualizado com sucesso!', product: updatedProduct }); } else { res.status(404).json({ message: 'Produto NoSQL não encontrado para atualização.' }); } } catch (error) { logger.error('Erro na rota PUT /nosql/products/:id:', error.message); // Em caso de erro de validação do Mongoose if (error.name === 'ValidationError') { return res.status(400).json({ message: 'Erro de validação ao atualizar produto.', details: error.message }); } res.status(500).json({ message: 'Erro ao atualizar produto NoSQL.', error: error.message }); } });

module.exports = router;

Como Testar e Validar o Resultado

Após iniciar sua aplicação Node.js (node app.js), você pode usar ferramentas como Postman ou Insomnia, ou mesmo curl, para testar as novas rotas.

Exemplo de Teste SQL (Usuário):

    • Crie um usuário primeiro:
      curl -X POST -H "Content-Type: application/json" -d '{"name": "Alice Silva", "email": "[email protected]"}' http://localhost:3000/api/sql/users
              

      Anote o id retornado.

    • Atualize o usuário (substitua ):
      curl -X PUT -H "Content-Type: application/json" -d '{"name": "Alice Oliveira", "email": "[email protected]"}' http://localhost:3000/api/sql/users/

      Você deve receber uma resposta com o usuário atualizado.

    • Verifique a atualização:
      curl http://localhost:3000/api/sql/users/

Exemplo de Teste NoSQL (Produto):

    • Crie um produto primeiro:
      curl -X POST -H "Content-Type: application/json" -d '{"name": "Teclado Mecanico", "description": "Teclado RGB com switches azuis", "price": 299.99, "category": "Perifericos", "stock": 50}' http://localhost:3000/api/nosql/products
              

      Anote o _id retornado.

    • Atualize o produto (substitua ):
      curl -X PUT -H "Content-Type: application/json" -d '{"price": 349.99, "stock": 45}' http://localhost:3000/api/nosql/products/

      Você deve receber uma resposta com o produto atualizado.

    • Verifique a atualização:
      curl http://localhost:3000/api/nosql/products/

Troubleshooting dos Erros Mais Comuns

    • “Connection refused”: Verifique se seu servidor MySQL/MongoDB está rodando e se as credenciais e o host no seu arquivo .env estão corretos. Para MongoDB Atlas, confira o IP de acesso permitido na configuração da rede.
    • “Validation error”: Verifique se os dados que você está enviando via POST ou PUT correspondem ao schema do seu modelo (por exemplo, name e email como strings, price como número). No Mongoose, required: true e min: 0 ativam validações.
    • “Cannot read property ‘execute’ of undefined”: Isso geralmente significa que a conexão SQL não foi estabelecida corretamente. Verifique connectSQL() e getSQLConnection().
    • “Cast to ObjectId failed”: Ocorre em MongoDB quando você tenta buscar ou atualizar um documento com um _id inválido ou mal formatado. O _id do MongoDB é um string hexadecimal de 24 caracteres.

Próximos Passos Sugeridos

Para aprimorar ainda mais seus conhecimentos:

    • ORM/ODM: Explore ORMs completos como Sequelize ou TypeORM para SQL, que oferecem uma camada de abstração mais robusta e recursos avançados como migrations e associações complexas.
    • Segurança: Implemente autenticação e autorização (por exemplo, JWT) para proteger suas rotas.
    • Otimização de Consultas: Aprenda sobre índices em SQL e NoSQL para melhorar a performance de suas consultas.
    • Backup e Recuperação: Investigue estratégias de backup e recuperação para ambos os tipos de banco de dados.
    • Transações: Entenda como lidar com transações em bancos de dados SQL para garantir a integridade de operações complexas, e as abordagens de “transações” em NoSQL (que muitas vezes são mais complexas ou exigem um design diferente, como transações distribuídas ou de dois-fases em MongoDB).

Parabéns por chegar até aqui! Você agora possui uma base sólida para discernir entre SQL e NoSQL e iniciar a integração com suas APIs Node.js/Express. Continue experimentando e construindo!

🚀 Pronto para a próxima aula?

Continue sua jornada no desenvolvimento de APIs e domine Node.js & Express!

📚 Ver todas as aulas