Fullstack com Node.js, React e GraphQL - 3: PostgreSQL e Sequelize
Olá, pessoal! Neste terceiro post da série Fullstack com Node.js, React e GraphQL vamos adicionar o banco de dados PostgreSQL, fazendo o mapeamento entre o banco e a aplicação com Sequelize.
Esquema do banco
Nosso banco de dados vai começar simples:
Esquema do banco
Temos a representação da Corretora (Broker). Cada corretora possui N Investimentos (Investments), e cada investimento pode possuir N Transações (Transactions), que são depósitos e retiradas feitas naquele investimento, e N Atualizações de Saldo (Balance Updates), que são mudanças no saldo do investimento decorrentes do rendimento daquela aplicação.
PostgreSQL
Vamos guardar estes dados em um banco PostgreSQL, pelos motivos:
- É gratuito e bem difundido, com bastante documentação disponível.
- É um dos bancos suportados pelo Sequelize, que era outra ferramenta que queria usar para aprendizado.
- O Heroku, serviço onde vamos colocar a aplicação em produção, oferece uma instância de PostgreSQL gratuita.
Instalação local
Em produção vamos usar o banco fornecido pelo Heroku, mas para desenvolvimento vamos precisar ter um banco local.
No site oficial do PostgreSQL tem opções de download para seu sistema operacional e instruções de instalação. No MacOS eu uso e recomendo o Postgres.app, uma interface simples para instalar e executar o Postgres.
Se você curte Docker, uma opção é usar um container com a imagem do Postgres. Não vou entrar em detalhes aqui para o post não ficar longo.
Criação do usuário no banco
Antes de começar a criar a tabelas, precisamos criar um usuário específico para a nossa aplicação dentro do Postgres.
Evite usar o usuário padrão, chamado postgres, em suas aplicações, por motivo de segurança, pois ele tem todas as permissões no banco inteiro, podendo alterar e apagar tudo.
Vamos usar o comando psql
para acessar o banco.
Uma vez conectado, você vai estar em uma outra linha de comando, onde pode usar comandos específicos no Postgres. um deles é o du
, que lista os usuários existentes no banco:
Vamos criar o usuário mymoney para a aplicação (pode escolher uma senha mais segura 😉):
CREATE ROLE mymoney WITH LOGIN PASSWORD '123456';
E dar permissão para ele criar bancos de dados:
ALTER ROLE mymoney CREATEDB;
Se executar um du
novamente, vai ver o novo usuário criado. Você pode usar q
para sair do psql.
Sequelize
Sequelize é um ORM (Object-Relational Mapper) para Node.js. Eles faz o mapeamento de dados relacionais (armazenados tabelas, linhas e colunas) para objetos em JS. Ele permite criar, buscar, alterar e remover dados do banco usando objetos e métodos em JS, além de fazer alterações na estrutura das tabelas. Ele suporta os bancos PostgreSQL, MySQL, MSSQL e SQLite.
Instalação
Vamos instalar o Sequelize e o módulo pg, para trabalhar com Postgres:
npm i sequelize pg
Vamos instalar também a ferramenta de linha de comando do Sequelize (CLI) como dependência de desenvolvimento:
npm i -D sequelize-cli
Inicialização
Vamos usar o sequelize-cli para gerar alguns arquivos iniciais, mas antes vamos configurar onde ele vai criar esses arquivos. Vamos criar um arquivo .sequelizerc na raiz, com o conteúdo:
const path = require('path')
module.exports = {
config: path.resolve('config/database.js'),
'migrations-path': path.resolve('db/migrate'),
'seeders-path': path.resolve('db/seeds'),
'models-path': path.resolve('src/models'),
}
Já já explico o que são esses arquivos. Agora vamos rodar no terminal:
npx sequelize init
Este comando vai criar os arquivos iniciais do Sequelize.
npx é um comando que já vem com o NPM a partir da versão 5.2.0. Ele roda um executável que foi instalado via NPM, sem precisar passar o caminho do executável nem instalá-lo globalmente. No exemplo acima ele vai executar o sequelize que está nas nossas dependências (pasta node_modules).
Mais detalhes: https://medium.com/@maybekatz/introducing-npx-an-npm-package-runner-55f7d4bd282b
Configuração
O arquivo config/database.js contém as informações de conexão ao banco, separadas em 3 ambientes: desenvolvimento, testes e produção. Vamos configurar apenas o ambiente de desenvolvimento por enquanto:
development: {
username: 'mymoney',
password: '123456',
database: 'mymoney',
host: '127.0.0.1',
dialect: 'postgres'
},
E essa senha escancarada aí? Não podemos comitar isso.
Vamos criar um arquivo de senhas e outras configurações sigilosas, config/secret.js:
module.exports = {
DATABASE_PASSWORD: '123456',
}
Adicionar config/secret.js
no .gitignore e usá-lo nas configurações do banco:
const secret = require('./secret');
module.exports = {
development: {
username: 'mymoney',
password: secret.DATABASE_PASSWORD,
database: 'mymoney',
host: '127.0.0.1',
dialect: 'postgres'
},
test: ...,
production: ...
};
Se o ESLint reclamar que você está usando um arquivo ignorado, adicione no seu .eslintrc:
rules: {
'node/no-unpublished-require': 'off'
}
Criação do banco
Para criar o banco, executamos o comando:
npx sequelize db:create
Modelos
Vamos criar nosso primeiro modelo: Broker (Corretora). Basta executar:
npx sequelize model:generate --name Broker --attributes name:string
Este comando vai criar dois arquivos: src/models/broker.js e db/migrate/20180311115229-create-broker.js
O primeiro é o modelo em si, que detalha os atributos do objeto Broker, seus tipos, regras de validação e relacionamento com outros modelos.
O segundo é o arquivo de migração, que descreve como esta alteração deve ser feita no banco de dados (no caso, será feita a criação de uma nova tabela).
Migracão (ou migration) é o processo alterar a estrutura de um banco de dados (criar/remover tabelas, colunas, alterar tipos e relacionamentos) de forma incremental e reversiva, e minimizando o impacto nos dados existentes.
Vamos fazer algumas alterações manuais nestes arquivos. No modelo, vamos adiciona uma validação no campo name
para que ele seja obrigatório:
name: { type: DataTypes.STRING, allowNull: false }
Vamos fazer a mesma coisa no arquivo de migração, mas desta vez para adicionar a validação a nível do banco de dados:
name: {
allowNull: false,
type: Sequelize.STRING
},
Segundo modelo e relacionamento
O próximo modelo, Investment (Investimento), tem uma peculiaridade. Ele tem uma relação com o Broker (Corretora), já que uma Corretora possui muitos Investimentos e um Investimento pertence a uma Corretora.
Primeiro, a geração dos arquivos de modelo e migração:
npx sequelize model:generate --name Investment --attributes name:string
Assim como no Broker, vamos adicionar a validação de obrigatoriedade no campo name, e também vamos especificar os relacionamentos.
No modelo de Investimento, dizemos que ele pertence a (belongs to) Corretora:
Investment.associate = function (models) {
this.belongsTo(models.Broker)
}
E no arquivo de migração de Investimento adicionamos o campo BrokerId, que faz a ligação com a tabela de Corretora (chave estrangeira):
BrokerId: {
allowNull: false,
type: Sequelize.INTEGER,
onDelete: 'CASCADE',
references: {
model: 'Brokers',
key: 'id'
}
},
Por fim, no modelo de Corretora dizemos que ele possui muitos (has many) Investimentos:
Broker.associate = function (models) {
this.hasMany(models.Investment)
}
Outros modelos
Para finalizar, a criação dos outros modelos e migrações:
npx sequelize model:generate --name Transaction --attributes amount:decimal,date:dateonly npx sequelize model:generate --name BalanceUpdate --attributes amount:decimal,date:dateonly
Lembrando de adicionar as relações: Investimento possui muitas Transações e Investimento possui muitas Atualizações de Saldo.
Nos modelos e migrações, além das validações de obrigatoriedade, também adicionei precisão nos campos de valores:
amount: { type: DataTypes.DECIMAL(16, 2), allowNull: false }
Você pode ver aqui o resultado final dos modelos:
https://github.com/doug2k1/my-money/tree/v2.0.0/src/models
e migrações:
https://github.com/doug2k1/my-money/tree/v2.0.0/db/migrate
Executando as migrações
Ok, os modelos estão descritos no nosso código, mas ainda precisamos fazer as alterações no banco:
npx sequelize db:migrate
Para conferir que as tabelas foram criadas, podemos acessar o psql com o usuário mymoney, listar as tabelas com dt
e ver as informações de cada tabela com d+ "NomeDaTabela"
:
psql -U mymoney
mymoney=> \dt
mymoney=> \d+ "Brokers"
Testando tudo
Para testar nosso banco e modelos, podemos adicionar o seguinte código no nosso server (src/index.js):
const { Broker, Investment, Transaction, BalanceUpdate } = require('./models')
const test = async () => {
// create broker
const broker = await Broker.create({ name: 'Fooinvest' })
// create investment
const investment = await Investment.create({
name: 'Tesouro Foo',
BrokerId: broker.get('id'),
})
// create transaction
await Transaction.create({
amount: 500,
date: '2018-03-10',
InvestmentId: investment.get('id'),
})
// create balance update
await BalanceUpdate.create({
amount: 501,
date: '2018-03-12',
InvestmentId: investment.get('id'),
})
// select all
const brokerWithDetails = await Broker.findOne({
include: [
{
model: Investment,
include: [{ model: Transaction }, { model: BalanceUpdate }],
},
],
})
console.log(JSON.stringify(brokerWithDetails))
}
test()
Ele importa os modelos e usa as funções do Sequelize para criar entidades (ex: Broker.create
) e buscar no banco (ex: Broker.findOne
).
Mais detalhes da API de model do Sequelize e todas as funções disponíveis para criação de busca de dados: http://docs.sequelizejs.com/manual/tutorial/models-usage.html
Vamos explorar mais estes modelos nas próximas partes.
Resultado final
O código do projeto até este ponto está em: https://github.com/doug2k1/my-money/tree/v2.0.0
No próximo capítulo
Na próxima parte vamos criar a interface administrativa usando Forest Admin. Stay tuned!
Feedbacks?
E aí, o que está achando até agora? Algo que precisa melhorar?
Tags: full-stack|graphql|nodejs|react