ORM Sequelize com database existente do Postgres no Node.js

Neste post irei abordar a utilização do Sequelize com um database existente do Postgres.

Devido a experiências vivenciadas com banco de dados relacionais e percebendo que o mesmo vive no ecossistema do Nodejs resolvi estudar um pouco sobre ORM’s, que dão suporte ao SGDB Postgres.

Após várias pesquisas cheguei no ORM Sequelize, um ORM completo para banco de dados SQL, que atende ao MySql, SQLite, MariaDB e MSSQL.

Verificações de instalações!

Antes de começar é necessário certificar que o Nodejs está instalado. No Postgres iremos trabalhar com duas tabelas conforme figura abaixo:

Database Tabelas Produto e Setor.

Colocando a mão na massa

Para iniciar o projeto crie o diretório database_existente:

$ mkdir database_existente
$ cd database_existente

Utilizei o npm init para inicializar o projeto (irá criar o package.json)

$ npm init -y

Utilizei o “-y” para ignorar perguntas sobre o projeto.

Instalei o Sequelize e também as dependências para o Postgres

$ npm i sequelize pg pg-hstore --save

E também instalei o framework Express, que é um framework para aplicativo da web do Nodejs mínimo e flexível que fornece um conjunto robusto de recursos para aplicativos web e móvel.

$ npm i express --save

Setup conexão Postgres

Criei uma pasta com nome config

$ mkdir config
$ cd config

Adicionei um arquivo JSON com o nome config.json, ficando assim:

{
    "development": {
        "dialect": "postgres",
        "port": 5432,
        "host": "localhost",
        "schema": "public",
        "database": "database_existente",
        "username": "postgres",
        "password": "postgres",
        "logging": false
    }
}

logging == false desativando o console.log do Sequelize

Estrutura de pastas

Model-view-controller (MVC).

  • Modelo um lugar para definir estruturas de dados e métodos para interagir com seu armazenamento de dados.
  • Visão um lugar para gerenciar tudo o que o usuário final vê em sua tela.
  • Controlador um lugar para receber solicitações de usuários, trazer dados do modelo e repassá-los para a visualização.

Para ficar organizado, incluí uma pasta chamada modulos dentro da raíz do projeto

$ mkdir modulos
$ cd modulos

Conforme figura abaixo:

estruturaPasta

Schemas

Adicionei o arquivo chamado setor.js dentro do diretório modulos/setor/model/

'use strict'

module.exports = (sequelize, DataTypes) => {

    const Setores = sequelize.define('Setor', {
        id: {
            type: DataTypes.INTEGER,
            primaryKey: true,
            autoIncrement: true,
            field: 'id'
        },
        descricao: {
            type: DataTypes.STRING,
            field: 'descricao'
        }
    }, {
        freezeTableName: true,
        schema: 'public',
        tableName: 'setor',
        timestamps: false
    })

    return Setores
}

O arquivo produto.js dentro do diretório modulos/produto/model/

'use strict'

module.exports = (sequelize, DataTypes) => {

    var Produtos = sequelize.define('Produto', {
        id: {
            type: DataTypes.INTEGER,
            primaryKey: true,
            autoIncrement: true,
            field: 'id'
        },
        descricao: {
            type: DataTypes.STRING,
            field: 'descricao'
        },
        barra: {
            type: DataTypes.STRING,
            field: 'barra'
        },
        id_setor: {
            type: DataTypes.INTEGER,
            field: 'id_setor'
        }
    }, {
        freezeTableName: true,
        schema: 'public',
        tableName: 'produto',
        timestamps: false,
        classMethods: {
            associate: (model) => {
                Produtos.belongsTo(model.Setor, { foreignKey: 'id_setor' })
            }
        }
    })

    return Produtos
}
  • freezeTableName == true sequelize não tentará alterar o nome DAO para obter o nome da tabela. Caso contrário, o nome do modelo será pluralizado;
  • timestamps == false Não adicionará as colunas createdAt e updatedAt timestamps para o modelo;
  • classMethods Fornece funções que são adicionadas ao modelo (Model). Se você substituir métodos fornecidos pelo sequelize, você pode acessar o método original usando this.constructor.prototype, This.constructor.prototype.find.apply (this, arguments);

Carregamento dos modelos e conexão do Sequelize com Postgres

Incluí o arquivo modelLoader.js dentro do diretório utils/

'use strict'

const env = process.env.NODE_ENV || 'development'
const config = require('../config/config.json')[env]
const path = require('path')
let Sequelize = require('sequelize')

//Criando a conexão com o banco de dados de acordo com a configuração do config.json
let sequelize = new Sequelize(config.database, config.username, config.password, config)
let db = {}

//Criando um array do caminho dos modelos
const models = [
    '../modulos/setor/model/setor',
    '../modulos/produto/model/produto',
]

let l = models.length

//Irá importar os modelos para o sequelize
for (let i = 0; i < l; i++) {
    let model = sequelize.import(path.join(models[i]))
    db[model.name] = model
}

//Irá percorrer e separar apenas o objeto que contém a propriedade associate, sem o "associate" o sequelize não monta uma relação!
Object.keys(db).forEach((modelName) => {
    if ('associate' in db[modelName])
        db[modelName].associate(db)
})

db.sequelize = sequelize
db.Sequelize = Sequelize

module.exports = db

Regras e solicitações dos usuários

Criei o arquivo setor.js dentro do diretório modulos/setor/controller/

'use strict'

const model = require('../../../utils/modelLoader')

exports.read = (req, res) => {

    model.Setor.findAll(}

    }).then((data) => {

        res.send(data)

    }).catch((error) => {
        console.log(error)
        res.send(error)
    });
};

exports.insert = (req, res) => {

    const dados = req.body

    model.Setor
        .build(
            dados
        )
        .save()
        .then((data) => {
            res.send(true)
        }).catch((error) => {
            console.log(error)
            res.send(false)
        })
}

exports.update = (req, res) => {

    const dados = req.body

    model.Setor
        .update(dados, {
            where: {
                id: req.query.id
            }
        })
        .then((data) => {
            res.send(true)
        }).catch((error) => {
            console.log(error)
            res.send(false)
        })
}

exports.delete = (req, res) => {

    const dados = req.body

    model.Setor
        .destroy({
            where: {
                id: dados.params.id
            }
        })
        .then((rowDeleted) => {
            res.send(true)
        }, (err) => {
            console.log(err)
            res.send(false)
        })
}

O arquivo produto.js dentro do diretório modulos/produto/controller/

'use strict'

const model = require('../../../utils/modelLoader')

exports.read = (req, res) => {

    model.Produto.findAll(}
        include: [
            { model: model.Setor }
        ]
    }).then((data) => {

        res.send(data)

    }).catch((error) => {
        console.log(error)
        res.send(error)
    })
}

exports.insert = (req, res) => {

    const dados = req.body

    model.Produto
        .build(
            dados
        )
        .save()
        .then((data) => {
            res.send(true)
        }).catch((error) => {
            console.log(error)
            res.send(false)
        })
}

exports.update = (req, res) => {

    const dados = req.body

    model.Produto
        .update(dados, {
            where: {
                id: req.query.id
            }
        })
        .then((data) => {
            res.send(true)
        }).catch((error) => {
            console.log(error)
            res.send(false)
        });
};

exports.delete = (req, res) => {

    const dados = req.body

    model.Produto
        .destroy({
            where: {
                id: dados.params.id
            }
        })
        .then((rowDeleted) => {
            res.send(true)
        }, (err) => {
            console.log(err)
            res.send(false)
        })
}

Caminhos de rota

Criei os roteamentos onde definimos às solicitações do cliente. Para obter uma introdução a roteamento, consulte Roteamento básico.

Incluí o arquivo setor.js dentro do diretório modulos/setor/routes/

'use strict'

const express = require('express')
const router = express.Router()

const controller = require('../controller/setor')

router.get('/listSetor', controller.read)
router.post('/saveSetor', controller.insert)
router.post('/updateSetor', controller.update)
router.post('/deleteSetor', controller.delete)

module.exports = router

O arquivo produto.js dentro do diretório modulos/produto/routes/

'use strict'

const express = require('express')
const router = express.Router()

const controller = require('../controller/Produto')

router.get('/listProduto', controller.read)
router.post('/saveProduto', controller.insert)
router.post('/updateProduto', controller.update)
router.post('/deleteProduto', controller.delete)

module.exports = router

Adicionei o arquivo setor.js dentro do diretório modulos/setor/

'use strict'

const modelLoader = require('../../utils/modelLoader')

const routerSetor = require('./routes/setor')

const models = [
    '../modulos/setor/model/setor'
]

exports.init = (app) => {

    app.use('/', routerSetor)

}

O arquivo produto.js dentro do diretório modulos/produto/

'use strict'

const modelLoader = require('../../utils/modelLoader')

const routerProduto = require('./routes/produto')

const models = [
    '../modulos/produto/model/produto'
]

exports.init = (app) => {

    app.use('/', routerProduto)

}

Iniciando um servidor e escuta a porta 3000 por conexões

Instalei o body-parser que analisa os corpos de solicitação de entrada em um middleware antes de seus manipuladores, disponíveis na propriedade req.body.

$ npm i body-parser --save

Incluí o arquivo app.js dentro da raíz do projeto

'use strict'

const express = require('express')
const path = require('path')
const bodyParser = require('body-parser')

const setor = require('./modulos/setor/setor')
const produto = require('./modulos/produto/produto')

const app = express()

app.use(bodyParser.json())
app.use(bodyParser.urlencoded({ extended: false }))

setor.init(app)
produto.init(app)

module.exports = app

Criei uma pasta bin na raíz do projeto.

$ mkdir bin

E o arquivo www dentro do diretório bin/

'use strict'

const app = require('../app')
const http = require('http')
const loader = require('../utils/modelLoader')

// Obter porta do meio ambiente e armazenamento no Express.

const port = normalizePort('3000')
app.set('port', port)

// Criar servidor HTTP.

let server = http.createServer(app)

// Escutar na porta todas as interfaces de rede.

server.listen(port)
server.on('error', onError)
server.on('listening', onListening)

// Normalizar uma porta em um número, string, ou falso.
function normalizePort(val) {
    const port = parseInt(val, 10)

    if (isNaN(port)) {
        // pipe nomeado
        return val
    }

    if (port >= 0) {
        // número da porta
        return port
    }

    return false
}

// Ouvinte de eventos para o servidor HTTP evento "erro".
function onError(error) {
    if (error.syscall !== 'listen') {
        throw error
    }

    const bind = typeof port === 'string' ?
        'Pipe ' + port :
        'Port ' + port

    // Ouvir erros com mensagens amigáveis
    switch (error.code) {
        case 'EACCES':
            console.error(bind + ' exige privilégios elevados')
            process.exit(1)
            break
        case 'EADDRINUSE':
            console.error(bind + ' já está em uso')
            process.exit(1)
            break
        default:
            throw error
    }
}

// ouvir eventos para o servidor HTTP.
function onListening() {
    const addr = server.address()
    const bind = typeof addr === 'string' ?
        'pipe ' + addr :
        'port ' + addr.port
    console.log('Listening on ' + bind)

}

Prontinho…

Para rodar a api

database_existente$ node bin/www
Listening on port 3000

Estou disponibilizando o projeto no GitHub e também a documentação das requisições para testes, acesse lá e baixe o mesmo.

Para testar as requisições, utilizo a ferramenta Postman.

Wharley Ornelas

Wharley Ornelas

Meu nome é Wharley Ornelas. Desenvolvedor Full-Stack, com mais de 15 anos de experiência de software. Membro ativo em comunidade de desenvolvimento..