Leodario.com

Leodario.com – Tudo sobre Tecnologia

Aula 57 – API JavaScript, Node.js e Express – Seeding – Dados iniciais

Imagem destacada da aula de API

Introdução

Bem-vindos, prezados estudantes, à Aula 57 do nosso aclamado curso de APIs! Hoje, vamos explorar um tópico que é verdadeiramente essencial para a robustez e a facilidade de desenvolvimento de qualquer aplicação moderna: o Seeding.

Imagine que você está inaugurando uma livraria novinha em folha. As prateleiras estão impecáveis, o sistema de caixa está configurado, mas… as prateleiras estão vazias! Ninguém vai querer visitar uma livraria sem livros, certo? O conceito de “seeding” para uma API é precisamente isso: o ato de preencher as “prateleiras” do seu banco de dados com “livros” iniciais (dados), antes que os usuários comecem a adicionar os seus próprios.

Para APIs modernas, ter dados iniciais é vital por uma série de razões. Em desenvolvimento, permite que você teste suas rotas e funcionalidades imediatamente, sem precisar criar dados manualmente a cada vez. Em ambientes de homologação ou demonstração, garante que sempre haverá um conjunto consistente de informações para apresentar. Em cenários mais específicos de produção, pode ser a forma de inserir dados configuracionais ou registros mestres.

Nesta aula, você não apenas compreenderá o que é o seeding, mas também aprenderá como desenvolver, implementar e gerenciar scripts de seeding de forma prática e eficaz dentro do ecossistema Node.js e Express. Veremos como esses scripts se integram com suas aplicações, utilizando bibliotecas de banco de dados e seguindo as melhores práticas de desenvolvimento.

Preparados para semear seus bancos de dados com inteligência e estratégia? Então, vamos adiante!

Conceito Fundamental

O termo técnico Seeding (do inglês “semear”) refere-se ao processo programático de popular um banco de dados com um conjunto de dados iniciais ou dados de demonstração. Esses dados não são gerados pela interação do usuário, mas sim inseridos intencionalmente por meio de scripts para estabelecer um estado inicial consistente para a aplicação ou ambiente.

Em sua essência, o seeding é a fase em que preparamos o terreno, garantindo que o banco de dados não esteja completamente vazio. Pense em um jogo novo: ele já vem com alguns personagens, itens ou cenários pré-definidos para você começar a interagir. O seeding faz exatamente isso para sua API e seu banco de dados.

Terminologia Relevante da Indústria

    • Seeders: São os scripts ou as funções que contêm a lógica para inserir os dados. Eles orquestram a conexão com o banco de dados, definem os dados a serem inseridos e executam as operações de gravação.
    • Fixtures: Arquivos (geralmente em formatos como JSON, YAML ou JavaScript puro) que armazenam os próprios dados a serem semeados. Eles funcionam como “templates” ou “modelos” para os registros que serão adicionados ao banco.
    • Rollback (ou Desfazer): Embora não seja uma funcionalidade primária de todos os seeders, a capacidade de reverter as operações de seeding (ex: apagar os dados semeados) é valiosa para ambientes de teste, onde você pode precisar reiniciar o estado do banco.

Casos de Uso Reais em Produção (e Desenvolvimento)

    • Ambientes de Desenvolvimento: Esta é a aplicação mais comum e, sem dúvida, a mais valiosa. Desenvolvedores podem rapidamente configurar seus bancos de dados locais com dados realistas para testar funcionalidades, prototipar novas características e depurar problemas sem a tediosa tarefa de criar dados manualmente.
    • Testes de QA e Homologação: Equipes de Qualidade de Software (QA) necessitam de ambientes consistentes para executar testes automatizados e manuais. O seeding garante que cada teste comece com o mesmo conjunto de dados, tornando os resultados mais confiáveis e reprodutíveis.
    • Ambientes de Demonstração: Ao apresentar sua aplicação a potenciais clientes ou investidores, você precisa de dados convincentes que demonstrem todo o potencial do sistema. O seeding habilita a criação de ambientes de demonstração ricos e informativos.
    • Dados Essenciais em Produção: Em casos mais raros e específicos, o seeding é utilizado para inserir dados que são indispensáveis para o funcionamento da aplicação em produção. Exemplos incluem categorias predefinidas (ex: “eletrônicos”, “moda”), configurações globais do sistema, ou a criação de um usuário administrador inicial. É crucial ter extremo cuidado ao semear dados em produção para evitar sobreposições ou inconsistências.

Integração com Outras Tecnologias

O seeding se integra perfeitamente com ORMs (Object-Relational Mappers) como Mongoose (para MongoDB) ou Sequelize (para bancos de dados relacionais como PostgreSQL, MySQL). Essas bibliotecas simplificam a interação com o banco de dados, permitindo que os seeders manipulem objetos JavaScript em vez de consultas SQL complexas. Ele também pode ser parte de seu pipeline de CI/CD (Integração Contínua/Entrega Contínua), sendo executado automaticamente em ambientes específicos.

Vantagens e Desvantagens

    • Vantagens:
      • Agilidade no Desenvolvimento: Acelera o início do trabalho em novas funcionalidades.
      • Consistência: Garante que todos os ambientes (desenvolvimento, teste) tenham dados padronizados.
      • Facilidade de Teste: Simplifica a criação de cenários de teste específicos.
      • Manutenibilidade: Os dados são versionados junto com o código, facilitando a atualização.
      • Dados Críticos: Possibilita a inserção de informações básicas para o funcionamento do sistema.
    • Desvantagens:
      • Complexidade de Gerenciamento: Se os dados iniciais mudam frequentemente, os seeders podem se tornar complexos de manter.
      • Risco em Produção: Executar seeders descuidadamente em produção pode levar à perda ou corrupção de dados existentes. É preciso um controle rigoroso.
      • Desempenho: Para grandes volumes de dados, o seeding pode ser demorado.

Implementação Prática

Agora, vamos colocar as mãos na massa e desenvolver um script de seeding funcional para uma aplicação Node.js/Express utilizando MongoDB com Mongoose. Nosso objetivo será popular uma coleção de “produtos” com dados de demonstração.

Para este exemplo, vamos assumir que você tem um banco de dados MongoDB acessível (localmente ou via um serviço como MongoDB Atlas) e um projeto Node.js inicializado. Certifique-se de ter o mongoose e o dotenv instalados: npm install mongoose dotenv.

Estrutura do Projeto

Organizamos nosso projeto de forma modular para facilitar a manutenção e a escalabilidade:


project-root/
├── .env
├── package.json
├── src/
│   ├── models/
│   │   └── Product.js
│   ├── db/
│   │   └── connection.js
│   └── data/
│       └── products.json
└── seeders/
    └── productSeeder.js
    └── index.js

1. Configuração do Ambiente (.env)

Crie um arquivo .env na raiz do seu projeto para armazenar variáveis de ambiente sensíveis, como a string de conexão do banco de dados.


.env

📚 Informações da Aula

Curso: API Completo - Node.js & Express

Tempo estimado: 25 minutos

Pré-requisitos: JavaScript básico

MONGODB_URI=mongodb://localhost:27017/minhaapi_dev

2. Definição do Modelo Mongoose (src/models/Product.js)

Este é o esquema do nosso produto. Ele define a estrutura dos documentos na coleção products do MongoDB.


// src/models/Product.js
const mongoose = require('mongoose');

// Define o esquema do produto const productSchema = new mongoose.Schema({ name: { type: String, required: true, trim: true // Remove espaços em branco do início e fim }, description: { type: String, required: false, trim: true }, price: { type: Number, required: true, min: 0 // Preço não pode ser negativo }, category: { type: String, required: true, enum: ['Eletrônicos', 'Roupas', 'Livros', 'Alimentos', 'Casa'], // Categorias permitidas trim: true }, stock: { type: Number, required: true, min: 0, // Estoque não pode ser negativo default: 0 }, createdAt: { type: Date, default: Date.now }, updatedAt: { type: Date, default: Date.now } });

// Atualiza a data de 'updatedAt' antes de salvar o documento productSchema.pre('save', function(next) { this.updatedAt = Date.now(); next(); });

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

module.exports = Product;

3. Conexão com o Banco de Dados (src/db/connection.js)

Um módulo para estabelecer e gerenciar a conexão com o MongoDB.


// src/db/connection.js
const mongoose = require('mongoose');
require('dotenv').config(); // Carrega as variáveis de ambiente

const connectDB = async () => { try { // Tenta conectar ao MongoDB usando a URI do arquivo .env await mongoose.connect(process.env.MONGODB_URI, { useNewUrlParser: true, // Opções para evitar avisos de deprecation useUnifiedTopology: true, // useCreateIndex: true, // Já não é mais suportado ou necessário em versões recentes do Mongoose // useFindAndModify: false // Já não é mais suportado ou necessário em versões recentes do Mongoose }); console.log('--- [MongoDB] Conectado com sucesso ao banco de dados! ---'); } catch (error) { // Captura e loga qualquer erro na conexão console.error('--- [MongoDB] Erro ao conectar ao banco de dados:', error.message); process.exit(1); // Encerra o processo com erro } };

const disconnectDB = async () => { try { // Desconecta do MongoDB await mongoose.disconnect(); console.log('--- [MongoDB] Desconectado do banco de dados. ---'); } catch (error) { console.error('--- [MongoDB] Erro ao desconectar do banco de dados:', error.message); process.exit(1); } };

module.exports = { connectDB, disconnectDB };

4. Dados Iniciais (Fixtures) (src/data/products.json)

Nosso array de objetos JavaScript que representa os dados que queremos inserir.


// src/data/products.json
[
    {
        "name": "Smartphone XPTO Max",
        "description": "O mais novo smartphone com câmera de 108MP e processador ultrarrápido.",
        "price": 1299.99,
        "category": "Eletrônicos",
        "stock": 50
    },
    {
        "name": "Camiseta Algodão Orgânico",
        "description": "Camiseta unissex feita com 100% algodão orgânico, super macia e durável.",
        "price": 49.90,
        "category": "Roupas",
        "stock": 200
    },
    {
        "name": "O Guia do Mochileiro das Galáxias",
        "description": "Um clássico da ficção científica que todo mundo deveria ler.",
        "price": 35.50,
        "category": "Livros",
        "stock": 120
    },
    {
        "name": "Notebook Gamer Power",
        "description": "Alta performance para jogos e trabalho, com placa de vídeo RTX 3080.",
        "price": 7500.00,
        "category": "Eletrônicos",
        "stock": 15
    },
    {
        "name": "Conjunto de Panelas Antiaderentes",
        "description": "Conjunto com 5 peças de panelas com revestimento antiaderente cerâmico.",
        "price": 320.00,
        "category": "Casa",
        "stock": 80
    }
]

5. Script de Seeder (seeders/productSeeder.js)

Este script será responsável por conectar ao banco, limpar dados existentes (opcional, mas boa prática em desenvolvimento) e inserir os novos dados.


// seeders/productSeeder.js
const { connectDB, disconnectDB } = require('../src/db/connection');
const Product = require('../src/models/Product');
const productData = require('../src/data/products.json'); // Importa os dados

const seedProducts = async () => { try { await connectDB(); // Conecta ao banco de dados

console.log('[Seeder: Products] Iniciando processo de seeding...');

// Melhor Prática Enterprise: Limpar dados existentes // Esta etapa é crucial para garantir que você esteja sempre começando com um estado limpo // ou para evitar duplicação de dados em ambientes de desenvolvimento. // EM PRODUÇÃO, esta linha DEVE SER MUITO BEM AVALIADA ou removida. await Product.deleteMany({}); console.log('[Seeder: Products] Todos os produtos existentes foram removidos.');

// Insere os novos dados await Product.insertMany(productData); console.log([Seeder: Products] ${productData.length} produtos foram inseridos com sucesso!);

} catch (error) { // Captura e loga erros durante o seeding console.error('[Seeder: Products] Erro durante o seeding de produtos:', error.message); // Log detalhado para depuração console.error(error); } finally { // Garante que a conexão com o banco seja fechada, independentemente do resultado await disconnectDB(); console.log('[Seeder: Products] Processo de seeding de produtos finalizado.'); } };

module.exports = seedProducts;

6. Script para Executar o Seeding (seeders/index.js)

Este arquivo servirá como um ponto de entrada para rodar um ou mais seeders.


// seeders/index.js
const seedProducts = require('./productSeeder');
// Poderíamos importar outros seeders aqui, ex: const seedUsers = require('./userSeeder');

const runSeeders = async () => { console.log('\n===== INICIANDO TODOS OS SEEDERS ====='); await seedProducts(); // await seedUsers(); // Se tivéssemos outros seeders console.log('===== TODOS OS SEEDERS FINALIZADOS =====\n'); };

runSeeders();

7. Adicionando o Script ao package.json

Para facilitar a execução, adicione um comando no seu package.json.


{
  "name": "minha-api",
  "version": "1.0.0",
  "description": "API de exemplo com seeding",
  "main": "index.js",
  "scripts": {
    "start": "node src/server.js",
    "seed": "node seeders/index.js", // Adicione esta linha!
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "dotenv": "^16.0.3",
    "express": "^4.18.2",
    "mongoose": "^7.0.0"
  }
}

Executando o Seeder

Para semear o banco de dados, simplesmente execute no seu terminal:


npm run seed

Você verá logs indicando a conexão, a remoção e a inserção dos produtos.

Configurações Específicas para HostGator Plano M

O HostGator Plano M é uma hospedagem compartilhada ou VPS. Para executar um script de seeding em um ambiente HostGator, você tipicamente precisará:

    • Acesso SSH: Conecte-se ao seu servidor via SSH. É através da linha de comando SSH que você executará o comando npm run seed.
    • Node.js Instalado: Verifique se a versão do Node.js no servidor é compatível com o seu projeto.
    • Variáveis de Ambiente: Certifique-se de que seu arquivo .env (ou as variáveis de ambiente equivalentes) estejam configuradas no servidor para apontar para o banco de dados correto. Para MongoDB, se o HostGator não oferece MongoDB nativo, você provavelmente usará um serviço externo como MongoDB Atlas, e a MONGODB_URI precisará refletir isso.
    • Recursos: Plano M pode ter limites de CPU/RAM. Para grandes volumes de dados, considere o impacto no desempenho. Seeders massivos podem consumir recursos.

A abordagem enterprise sempre evita rodar seeders automaticamente em ambientes de produção durante o deploy, a menos que sejam scripts de migração de dados cuidadosamente versionados.

Error Handling Sólido

Observe que nosso script productSeeder.js e connection.js incluem blocos try...catch. Isso é fundamental. Se houver um problema na conexão com o banco ou na inserção dos dados (ex: violação de esquema, dados inválidos), o erro será capturado, logado, e o processo será encerrado de forma controlada, sem travar a aplicação indefinidamente. Além disso, o bloco finally garante que a conexão com o banco seja sempre fechada, liberando recursos.

Testes Básicos

Após executar o seeder, você pode verificar se os dados foram inseridos de duas maneiras:

    • Diretamente no Banco de Dados: Use uma ferramenta como MongoDB Compass ou o shell do MongoDB para navegar até a coleção products no banco de dados minhaapi_dev e inspecionar os documentos.
    • Via um Endpoint da API (se existir): Se você tiver uma rota Express como /api/products que lista todos os produtos, você pode acessá-la para ver os dados semeados.

Exercício Hands-On

Agora é a sua vez de aplicar o que aprendemos. Vamos expandir nosso sistema de seeding para incluir uma nova entidade: Usuários.

Desafio Prático

Sua tarefa é criar um novo seeder para usuários. Isso envolve:

    • Definir um novo modelo Mongoose para User.
    • Criar um arquivo de dados (fixtures) para os usuários.
    • Desenvolver um script de seeder (userSeeder.js) para popular a coleção de usuários.
    • Integrar este novo seeder ao script principal (seeders/index.js) para que ele seja executado junto com o seeder de produtos.

Solução Detalhada Passo a Passo

Passo 1: Criar o Modelo User.js (src/models/User.js)

Crie o arquivo src/models/User.js com o seguinte conteúdo:


// src/models/User.js
const mongoose = require('mongoose');

const userSchema = new mongoose.Schema({ username: { type: String, required: true, unique: true, // Garante que cada username seja único trim: true }, email: { type: String, required: true, unique: true, // Garante que cada email seja único trim: true, lowercase: true, // Converte email para minúsculas match: [/^\w+([\.-]?\w+)@\w+([\.-]?\w+)(\.\w{2,3})+$/, 'Por favor, insira um email válido'] // Validação de formato }, password: { type: String, required: true, minlength: 6 // Senha deve ter no mínimo 6 caracteres }, role: { type: String, enum: ['user', 'admin'], // Papéis permitidos default: 'user' }, createdAt: { type: Date, default: Date.now } });

const User = mongoose.model('User', userSchema);

module.exports = User;

Passo 2: Criar Dados de Usuários (src/data/users.json)

Crie o arquivo src/data/users.json com alguns usuários de exemplo. Lembre-se que em uma aplicação real as senhas seriam hash, mas para o seeding de demonstração podemos usar senhas simples.


// src/data/users.json
[
    {
        "username": "adminUser",
        "email": "[email protected]",
        "password": "password123", // Em produção, use hash de senha!
        "role": "admin"
    },
    {
        "username": "john.doe",
        "email": "[email protected]",
        "password": "userpass",
        "role": "user"
    },
    {
        "username": "jane.smith",
        "email": "[email protected]",
        "password": "userpass",
        "role": "user"
    }
]

Passo 3: Desenvolver o Script userSeeder.js (seeders/userSeeder.js)

Crie o arquivo seeders/userSeeder.js, espelhando a estrutura do productSeeder.js.


// seeders/userSeeder.js
const { connectDB, disconnectDB } = require('../src/db/connection');
const User = require('../src/models/User');
const userData = require('../src/data/users.json');

const seedUsers = async () => { try { await connectDB(); // Conecta ao banco de dados (se já não estiver conectado)

console.log('[Seeder: Users] Iniciando processo de seeding...');

// Remove todos os usuários existentes para um ambiente limpo await User.deleteMany({}); console.log('[Seeder: Users] Todos os usuários existentes foram removidos.');

// Insere os novos dados de usuário await User.insertMany(userData); console.log([Seeder: Users] ${userData.length} usuários foram inseridos com sucesso!);

} catch (error) { console.error('[Seeder: Users] Erro durante o seeding de usuários:', error.message); console.error(error); // Log detalhado } finally { // A conexão será desconectada pelo script principal se este for o último seeder. // Se este fosse um seeder independente, teríamos disconnectDB() aqui. } };

module.exports = seedUsers;

Passo 4: Integrar ao Script Principal (seeders/index.js)

Modifique seeders/index.js para incluir o novo seeder de usuários.


// seeders/index.js
const { connectDB, disconnectDB } = require('../src/db/connection'); // Importa a conexão
const seedProducts = require('./productSeeder');
const seedUsers = require('./userSeeder'); // Importa o novo seeder de usuários

const runAllSeeders = async () => { // Conecta ao DB apenas uma vez para todos os seeders await connectDB();

console.log('\n===== INICIANDO TODOS OS SEEDERS ====='); try { await seedProducts(); // Executa o seeder de produtos await seedUsers(); // Executa o seeder de usuários // Adicione outros seeders aqui conforme necessário

} catch (error) { console.error('Um erro fatal ocorreu durante o seeding:', error.message); process.exit(1); } finally { // Desconecta do DB após a execução de todos os seeders await disconnectDB(); console.log('===== TODOS OS SEEDERS FINALIZADOS =====\n'); } };

runAllSeeders();

Como Testar e Validar o Resultado

Execute novamente o comando npm run seed. Você deverá ver os logs de ambos os seeders sendo executados. Após a execução:

    • Utilize o MongoDB Compass ou o shell para inspecionar as coleções products e users no seu banco de dados. Você deverá encontrar os dados que você definiu nos arquivos JSON.
    • Se você tiver endpoints da API para listar produtos (/api/products) e usuários (/api/users), acesse-os no navegador ou com uma ferramenta como Postman/Insomnia para verificar os dados.

Troubleshooting dos Erros Mais Comuns

    • Erro de Conexão com o DB: Verifique sua MONGODB_URI no arquivo .env. Certifique-se de que o servidor MongoDB esteja em execução e acessível. Firewalls podem bloquear conexões.
    • Schema Validation Error: Se você vir erros como “Product validation failed” ou “User validation failed”, significa que os dados no seu arquivo JSON não estão em conformidade com o esquema Mongoose que você definiu (ex: campo required faltando, tipo incorreto, enum inválido). Revise o modelo e os dados.
    • Duplicate Key Error (E11000): Isso ocorre quando você tenta inserir um documento com um valor para um campo que foi definido como unique, e esse valor já existe. Por exemplo, se tentar inserir um usuário com um email que já está no banco de dados. Nosso deleteMany({}) ajuda a evitar isso em desenvolvimento.
    • Arquivos Não Encontrados: Verifique os caminhos require() nos seus seeders e no script index.js. Os caminhos devem ser relativos ao arquivo que está fazendo o require.

Próximos Passos Sugeridos

    • Dados Aleatórios com faker.js: Em vez de criar JSONs estáticos, explore a biblioteca Faker.js para gerar dados de teste realistas e em grande volume. Isso é extremamente útil para testar paginação, filtros e desempenho com muitos registros.
    • Seeder de Limpeza (Rollback): Crie um script separado que tenha a função de apenas apagar as coleções seeded. Isso pode ser útil para reiniciar o ambiente de teste rapidamente sem rodar o seeder completo novamente.
    • Integração com Pipelines CI/CD: Explore como você pode integrar a execução de seeders em seu fluxo de Integração Contínua/Entrega Contínua, garantindo que os ambientes de teste sejam populados automaticamente.
    • Seeding Condicional: Adicione lógica aos seus seeders para que eles só insiram dados se uma condição específica for atendida (ex: se uma coleção estiver vazia, ou se um determinado ambiente estiver configurado).

Com este conhecimento e a prática adquirida, você está agora capacitado a gerenciar os dados iniciais de suas APIs de forma profissional e eficiente, acelerando seu desenvolvimento e garantindo a qualidade de suas entregas. Continue explorando e construindo!

🚀 Pronto para a próxima aula?

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

📚 Ver todas as aulas