# Construindo uma API Node Express com Sequelize ORM

### Requisitos

1. Node.js instalado. Caso não possua o node intalado ainda [clique aqui](https://nodejs.org/en/download)

### Criando aplicação e instalando as dependências

Vamos iniciar uma aplicação com o comando `npm init -y` e criar na pasta raiz os arquivos **server.js, bd.js** e quatro novas pastas chamadas **router, controller, model e service**.

Agora podemos instalar as dependências do projeto através do comando `npm install express dotenv pg postgres sequelize sequelize-cli cors bcrypt` .

Instale também como dependencias de desenvolvimento `npm install nodemon morgan --save-dev` .

Configure também um comando de inicialização da aplicação atrelado ao nodemon no package.json

Ao final do processo você deve ter a seguinte estrutura de arquivo e package.json.

<figure><img src="https://3159712424-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7u5Ie7aXnzkhZ3fwalBr%2Fuploads%2FJd1QNvuINRTFaxLETbvj%2Fpastas.png?alt=media&#x26;token=b5b6b6f8-49a1-4312-a3a1-6c8eff91acf2" alt=""><figcaption></figcaption></figure>

```json
{
  "name": "apiexemplo",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start":"nodemon start server.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "bcrypt": "^5.1.1",
    "cors": "^2.8.5",
    "dotenv": "^16.3.1",
    "express": "^4.18.2",
    "pg": "^8.11.3",
    "postgres": "^3.4.0",
    "sequelize": "^6.33.0",
    "sequelize-cli": "^6.6.1"
  },
  "devDependencies": {
    "morgan": "^1.10.0",
    "nodemon": "^3.0.1"
  }
}
```

### Banco de Dados

Crie um banco de dados local ou então siga op tutorial abaixo para criar uma instância do Elephant SQL.

[elephant-sql](https://lab-mackleaps.gitbook.io/residencia-de-software-macklaeps-upm-fci/recursos-educacionais/bancos-de-dados/elephant-sql "mention")

Agora vamos prosseguir com a configuração do banco no arquivo bd.js que criamos anteriormente. Importe o **sequelize** e do **dotenv** conforme mostrado. Crie também na pasta raiz do projeto um arquivo **.env**. Nesta etapa vamos utilizar a url copiada por você na última etapa do tutorial acima.

```javascript
const Sequelize = require("sequelize"); //importação do sequelize
require("dotenv").config(); //importação do .env com as variáveis de ambiente

const bdUser = process.env.user; //variavel de ambiente
const bdPassword = process.env.password; //variavel de ambiente

const sequelize = new Sequelize(
  `postgres://${bdUser}:${bdPassword}@silly.db.elephantsql.com/${bdUser}`, // sua url de conexão com o banco
  { dialect: "postgres" } //define o dialeto do banco
);

module.exports = sequelize; //exporte padrão do Node.
```

```javascript
#Dados BD
user="seuUsuario"
password="suaSenha"
```

### Configurando o servidor e definindp uma porta

Agora podemos configurar o servidor em **server.js**

```javascript
const express = require("express");
const cors = require("cors");
const morgan = require("morgan");
const bd = require("./bd");

const app = express(); //inicia o express
app.use(morgan("dev")); //imprime os logs das operações durante o desenvolvimento
app.use(cors()); //libera o cross origin
app.use(express.json()); // configura a API para receber e enviar json (Headers HTTP)

//rota padrão na qual podemos verificar se o servidor está disponível
app.get("/", async (req, res) => {
  res.status(200).json({ msg: "Api ok!" });
});


//inicia o servidor através de uma self-invoked function que executa na inicialização da aplicação
(async () => {
    try {
      await bd.sync({ force: false });// sincroniza o banco
      console.log("Banco conectado!");
      app.listen(3000, () => console.log("Service is live!")); //iniciar efetivamente o servidor na porta 3000
    } catch (err) {
      console.log(err); //imprime o erro caso este ocorra
    }
  })();
```

Observe o objeto passado de parâmetro na função que sincroniza o banco.

* **`force: true`**: Quando você define **`force`** como **`true`**, a sincronização do banco de dados recriará todas as tabelas, ou seja, ele irá descartar todas as tabelas existentes e criá-las novamente do zero. Isso é útil em situações de desenvolvimento ou testes, quando você deseja recriar o esquema do banco de dados para refletir as mudanças no seu modelo de dados ou quando deseja começar com um banco de dados vazio.
* **`force: false`**: Ao definir **`force`** como **`false`**, a sincronização do banco de dados não irá recriar as tabelas se elas já existirem. Isso é a configuração padrão e é mais apropriado para ambientes de produção, onde você deseja manter os dados existentes. Usar **`false`** é uma abordagem mais segura para evitar a perda de dados acidental, pois não apagará ou recriará tabelas existentes.

Em resumo, ao definir **`force: true`**, você está instruindo o sistema a recriar o esquema do banco de dados do zero, enquanto **`force: false`** garante que o esquema atual do banco de dados seja mantido, evitando a perda de dados. A escolha entre essas opções dependerá dos requisitos do seu ambiente, sendo **`true`** mais comum durante o desenvolvimento e **`false`** mais adequado para produção.

Podemos agora testar a conexão com o banco de dados iniciando a aplicação através do comando `npm start` . Se a conexão com o banco for bem sucedida a mensagem de "Banco conectado” será exibida no console bem como “Service is live!”que indica que o servidor está rodando.

```sh
mackleaps@222816-MackFCI apiExemplo % npm start

> apiexemplo@1.0.0 start
> nodemon start server.js

[nodemon] 3.0.1
[nodemon] to restart at any time, enter `rs`
[nodemon] watching path(s): *.*
[nodemon] watching extensions: js,mjs,cjs,json
[nodemon] starting `node start server.js`
Executing (default): SELECT 1+1 AS result
Banco conectado!
Service is live!
```

Também podemos verificar que a aplicação foi iniciada corretamente através da rota que definimos para esta finalidade no server. Em seu navegador use a url [http://localhot:3000](http://localhot:3000/) e verifique se a mensagem {msg:Api ok!} foi exibida.

<figure><img src="https://3159712424-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7u5Ie7aXnzkhZ3fwalBr%2Fuploads%2F4aq8A5JOOiCWzNt6rsPd%2Fnav1.png?alt=media&#x26;token=52280bf2-e8fb-45ba-ae53-1d709b6f7df9" alt=""><figcaption></figcaption></figure>

<figure><img src="https://3159712424-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7u5Ie7aXnzkhZ3fwalBr%2Fuploads%2F2QYp5ypOU1TSkmzfad5P%2Fnav2.png?alt=media&#x26;token=6b530b7b-ecbb-4362-9e4a-bbfdaea07463" alt=""><figcaption></figcaption></figure>

### Criando um Schema com Sequelize

Neste passo vamos criar o esquema para a tabela usuários de nossa aplicação. Crie um arquivo chamado userModel.js dentro da pasta model que criamos anteriormente.

```javascript
const sequelize = require("sequelize"); // importamos o sequelize que gerencia a criação da tabela
const db = require("../bd"); //importamos o banco no qual conectamos anteriormente

const User = db.define(
  "usuarios", //define o nome da tabela no banco de dados
  {
    id: {
      type: sequelize.INTEGER.UNSIGNED,
      primaryKey: true, //define que esta coluna é a chame primária
      autoIncrement: true, //incrmenta o id em uma unidade evitando duplicidade de chave primaria
      allowNull: false, //define que este atributo não pode ser vazio
    },
    nome: {
      type: sequelize.STRING,
      allowNull: false,
      unique: true, //define que todos os valores desta coluna deve ser único
    },
    password: {
      type: sequelize.STRING,
      allowNull: false,
    },
    permissao: {
      type: sequelize.STRING,
      allowNull: false,
    },
  },
  {
    timestamps: false, //desabilita o armazenamento de datas em que foi criado e atualizado
  }
);

module.exports = User;
```

Para entender melhor o funcionamento e os tipos de dados acesse a documentação do sequelize no link abaixo.

{% embed url="<https://sequelize.org/docs/v6/getting-started/>" %}

O sequelize também permite definir relacionamento entre as tabelas.

{% embed url="<https://www.youtube.com/watch?v=vJZ_kh7G8-k>" %}

### Criando o CRUD da API através de funções controller

Agora vamos criar as funções que são responsáveis por receber as requisições processa-las recuperando as informações do banco de dados e retornar um json como resposta para o cliente. Crie um arquivo **userController.js** na pasta **controller**.

Geralmente, um arquivo de controlador é responsável por conter todas as funções que manipulam as operações de CRUD (Create, Read, Update, Delete) lidando diretamente com as operações HTTP para um determinado modelo de dados em um aplicativo. Neste exemplo, o foco está em operações de usuário usando o modelo **`User`**.

1. **`recuperarUsuarios`**: Essa função lida com a operação de leitura para recuperar todos os usuários da tabela. Ela usa o método **`User.findAll()`** para buscar todos os registros e responde com os usuários encontrados ou uma mensagem de erro.
2. **`adicionarUsuario`**: Essa função lida com a operação de criação para adicionar um novo usuário à tabela. Ela desestrutura os dados do corpo da requisição, usa o método **`User.create()`** para criar um novo registro e responde com o novo usuário ou uma mensagem de erro.
3. **`findById`**: Esta função trata da operação de leitura para encontrar um usuário específico com base no ID. Ela extrai o ID da URL da rota, usa o método **`User.findByPk(id)`** para buscar o registro correspondente e responde com o usuário encontrado ou uma mensagem de erro.
4. **`excluiUsuario`**: Essa função lida com a operação de exclusão para remover um usuário com base no ID. Ela extrai o ID da URL da rota, usa o método **`User.destroy()`** para excluir o registro e responde com uma mensagem de sucesso ou uma mensagem de erro.
5. **`atualizarUsuario`**: Esta função trata da operação de atualização para modificar um usuário com base no ID. Ela extrai o ID da URL da rota e os novos dados do corpo da requisição. Ela também inicia uma transação para garantir a integridade do banco de dados, atualiza o registro com os novos dados e responde com uma mensagem de sucesso ou uma mensagem de erro.

Por fim, as funções são exportadas como um objeto no final do arquivo, tornando-as disponíveis para uso em outras partes do aplicativo.

```javascript
const User = require("../model/userModel"); //import do model de user que criamos anteiormente
const sequelize = require("../bd"); //importe do sequelize para definir controle de transações (Transaction Control Language, TCL)

//req-> paramentro que recebe a requisição
//res ->parametro que configura a resposta da requisição

//READ->GET
async function recuperarUsuarios(req, res) {
  try {
    const usuarios = await User.findAll(); //método findAll do sequelize retorna um sequelize object com todos os usuários do banco
    //verifica se usuarios for vazio retorna 404 not found como resposta
    if (usuarios.length === 0) {
      res.status(404).json({ msg: "Não foram encontados usuários" });
      return;
    }
    res.status(200).json(usuarios); //se a busca for bem sucedida retorna 200 e um array que contém json com as informações dos usuários
  } catch (err) {
    //tratamento de excessões
    console.log(err);
    res.status(500).json({ msg: "Falha ao recuperar usuários!" }); //caso algum erro ocorra retorna internal server error 500 junto da mensagem de erro
  }
}

//CREATE->POST
async function adicionarUsuario(req, res) {
  const { nome, password, permissao } = req.body; //desestruturação do objeto presente no corpo da requisição
  try {
    //recebe json como argumento do novo elemento que será criado e retorna elemento que foi criado
    const novoUsuario = await User.create({
      nome: nome,
      password: password,
      permissao: permissao,
    });
    res.status(201).json(novoUsuario); //retorna para o cliente código 201 created e o json do novo elemento
  } catch (err) {
    console.log(err);
    res.status(500).json({ msg: "Falha ao criar usuário!" }); //retorna internal server error 500 caso ocorra algum erro durante a criação
  }
}

//READ->GET

async function findById(req, res) {
  const { id } = req.params; //desestrutura a url da rota recuperando o id
  try {
    const usuario = await User.findByPk(id); // findByPk é um método do sequelize que pesquisa e retorna um objeto que corresponde aquela chave primária
    if (!usuario) {
      //verifica se usuario for vazio retorna 404 not found como resposta
      res.status(404).json({ msg: "Usuario não encontrado" });
      return;
    }
    res.status(200).json(usuario); //se a busca for bem sucedida retorna 200 e um json com as informações do usuário
  } catch (err) {
    res.status(500).json({ msg: "Falha ao criar usuário!" }); //retorna internal server error 500 caso ocorra algum erro durante a operação
  }
}

//DELETE->DELETE
async function excluiUsuario(req, res) {
  const { id } = req.params; //desestrutura a url da rota recuperando o id do elemnto a ser excluido
  try {
    //exclui a linha da tabela através do id passado na uri através do método destroy do sequelize
    await User.destroy({
      where: {
        id: id,
      },
    });
    res.status(200).json({ msg: "Usuário excluido!" });
  } catch (err) {
    console.log(err);
    res.status(500).json({ msg: "Falha ao criar usuário!" }); //retorna internal server error 500 caso ocorra algum erro durante a operação
  }
}

//UPDATE->PUT
async function atulizarUsuario(req, res) {
  const { id } = req.params; //desestrutura a url da rota recuperando o id do elemnto a ser atualizado
  const { novoNome, novoPassword, novaPermissao } = req.body; //desestruturação do objeto presente no corpo da requisição
  const t = await sequelize.transaction(); //abre uma transação para evitar que o banco perda sua integridade
  try {
    //update é metodo do sequelize que atualiza linha da tabela
    await User.update(
      {
        nome: novoNome,
        password: novoPassword,
        permissao: novaPermissao,
      },
      {
        where: { id: id },
        transaction: t, // Associa a transação à atualização
      }
    );
    // Confirme a transação
    await t.commit();
    res.status(200).json({ msg: "Usuário atualizado!" });
  } catch (err) {
    // Em caso de erro, reverta a transação
    await t.rollback();
    res.status(500).json({ msg: "Falha ao atualizar" });
  }
}

//Exportação das funções para acesso no router
module.exports = {
  recuperarUsuarios,
  adicionarUsuario,
  findById,
  excluiUsuario,
  atulizarUsuario,
};
```

### Definindo as rotas no Router

Em um aplicativo web, o roteamento desempenha um papel fundamental na definição de como as diferentes URLs são tratadas, quais ações devem ser executadas e como os recursos são servidos. Para facilitar essa funcionalidade em aplicativos Node.js, a biblioteca Express fornece um recurso chamado **Router**, que é uma parte essencial na criação de rotas e na manipulação de solicitações HTTP.

**O que é um Router?**

Um **Router** no contexto do Express é um módulo que permite organizar e mapear rotas em seu aplicativo Node.js. Ele funciona como um middleware que ajuda a direcionar solicitações HTTP para as funções de controle adequadas. Essas funções de controle executam a lógica de negócios necessária com base no caminho da URL e no método HTTP da solicitação.

**O que um Router Define?**

Um Router define como as rotas devem se comportar em relação a uma série de critérios, incluindo o método HTTP, a URL e qualquer parâmetro que possa estar presente na rota. Com um Router, você pode criar rotas para lidar com:

* **GET**: Para buscar informações.
* **POST**: Para criar novos recursos.
* **PUT**: Para atualizar recursos existentes.
* **DELETE**: Para excluir recursos.

### **Rotas Dinâmicas**

No Express, você pode definir rotas dinâmicas usando notações especiais, como **`:id`**. Essa notação indica que um valor variável será capturado a partir da URL e disponibilizado como um parâmetro na função de controle associada. Por exemplo, a rota **`/usuarios/:id`** permitirá que você acesse o valor **`id`** na função de controle quando um URL como **`/usuarios/123`** for acessado, onde **`123`** é o valor do parâmetro **`id`**.

A rota **`/produtos/:categoria?`** no Express define um parâmetro chamado **`categoria`** como opcional, permitindo que os clientes acessem a rota com ou sem um valor para **`categoria`**. Quando um valor é fornecido, ele é acessível na função de controle através de **`req.params.categoria`**, mas se nenhum valor for especificado na URL, o Express considera o valor padrão como "todos". Isso oferece flexibilidade ao usuário, tornando possível listar produtos de uma categoria específica quando desejado, ou todos os produtos quando a categoria não é especificada.

Agora podemos criar no diretório router o arquivo **userRouter.js** . Neste arquivo vamos mapear as funções do controller para as rotas e métodos HTTP.

1. As funções do controlador são importadas no início do arquivo **`routes.js`**.
2. O módulo Express é importado para criar e configurar o objeto **`Router`**.
3. Cada rota é definida usando o método correspondente (**`get`**, **`post`**, **`delete`**, **`put`**) no objeto **`routes`**. A função do controlador correspondente é associada a cada rota.
4. Nas rotas que exigem um parâmetro, como **`/usuarios/:id`**, a notação **`:id`** indica que esse parâmetro será parte da URL da requisição. Por exemplo, uma solicitação para **`/usuarios/123`** fornecerá o valor **`123`** como o parâmetro **`id`**.
5. Finalmente, o objeto **`routes`** é exportado para que ele possa ser utilizado no arquivo principal (geralmente **`server.js`**) para configuração do servidor Express.

```javascript
const express = require("express"); //importação do express
const routes = express.Router(); //importação do Router da biblioeca express

//importação das funções do controller que serão mapeadas para as rotas
const {
  recuperarUsuarios,
  adicionarUsuario,
  findById,
  excluiUsuario,
  atulizarUsuario,
} = require("../controller/userController");

//mapeamento das funções do controller para seus respectivos métodos HTTP e rotas
routes.get("/usuarios", recuperarUsuarios);
routes.post("/usuario", adicionarUsuario);
routes.get("/usuario/:id", findById); //notação :id indica que o parametro id sera informado na url da requisição
routes.delete("/usuario/:id", excluiUsuario); //notação :id indica que o parametro id sera informado na url da requisição
routes.put("/usuario/:id", atulizarUsuario); //notação :id indica que o parametro id sera informado na url da requisição

//exportamos o objeto routes
module.exports = routes;
```

### Configurando as rotas no aplicativo principal

Para concluir esta seção do guia, vamos integrar as rotas que definimos no router ao servidor, tornando-as finalmente acessíveis para uso por aplicativos externos.

No arquivo **`server.js`**, realize a importação do objeto **`routes`** e, em seguida, passe esse objeto como argumento para a função **`app.use()`**. Isso permitirá a integração das rotas definidas no arquivo de rotas com o aplicativo principal.

```javascript
const express = require("express");
const cors = require("cors");
const morgan = require("morgan");
const bd = require("./bd");

//importamos as rotas do usuário
const userRoutes = require("./router/userRouter");

const app = express(); //inicia o express
app.use(morgan("dev")); //imprime os logs das operações durante o desenvolvimento
app.use(cors()); //libera o cross origin
app.use(express.json()); // configura a API para receber e enviar json.
app.use(userRoutes); // torna as rotas  disponíveis para uso

//rota padrão na qual podemos verificar se o servidor está disponível
app.get("/", async (req, res) => {
  res.status(200).json({ msg: "Api ok!" });
});

//inicia o servidor através de uma self-invoked function
(async () => {
  try {
    await bd.sync({ force: false }); // sincroniza o banco
    console.log("Banco conectado!");
    app.listen(3000, () => console.log("Service is live!")); //iniciar efetivamente o servidor na porta 3000
  } catch (err) {
    console.log(err);
  }
})();
```

Agora execute novamente a aplicação através do comando npm start . Observe que o sequelize se encarregara da criação da tabela usuarios no banco.

<figure><img src="https://3159712424-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7u5Ie7aXnzkhZ3fwalBr%2Fuploads%2FZNDjvPCqBQPlZnV9KZEt%2Flogs.png?alt=media&#x26;token=319686bf-d2f3-4274-8c99-19cab59425ee" alt=""><figcaption></figcaption></figure>

Neste momento a API já está pronta para receber requisições.
