NodeJS: desenvolvendo uma API REST
Instalando o NodeJS
sudo apt install nodejs -y
# Checando se o node foi instalado
node -v
# Checando se o npm foi instalado
npm -v
## Se o npm não foi instalado, use o comando: sudo apt install npm -y
Criando Estrutura
mkdir node-api # node-api é o nome do projeto
cd node-api
npm init -y # criando projeto node
Teremos um arquivo chamado package.json
. Ele é responsável por manter metadados do projeto e informações sobre as dependências.
# Instalando express (usado para implementar as rotas de requisição)
npm install express --save
# O diretório criado node-modules armazena as dependências
Criando Primeira Rota
Criamos o arquivo server.js
e nele temos o seguinte conteúdo:
// Importando express
const express = require("express");
// Instanciando a função do express
const app = express();
// Rota
app.get("/", (req,res) => {
// send() : Envio de dados genérico
res.send("Hello World");
});
// Definindo porta localhost
app.listen(3001);
Utilizando Nodemon
# Instalando o Nodemon (atualização dinâmica com reinicialização automática da api)
npm install --save-dev nodemon
Editamos o arquivo package.json
:
{
...
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "nodemon server.js"
}
...
}
Usando o comando abaixo nós executamos o nodemon:
npm run dev
Instalando MongoDB
Instalaremos primeiro o Docker para isolar a instalação do MongoDB:
# Instalação do Docker em Ubuntu 18.04 Based Linux
sudo apt update
sudo apt install apt-transport-https ca-certificates curl software-properties-common
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu bionic stable"
sudo apt update
sudo apt install docker-ce
# Para verificar se já está ativo
sudo systemctl status docker
Instalando o MongoDB:
# Baixando container do mongo
docker pull mongo
# Subindo container com mongo
docker run --name mongodb -p 27017:27017 -d mongo
# --name nome que será chamado o container
# -p porta de origem nossa máquina : porta de destino no container
# -d imagem que iremos subir
Alguns comandos do Docker:
# Verificamos as imagens que estão rodando
docker ps
# Verificamos todas as imagens, estejam rodando ou pausadas
docker ps -a
# Iniciar imagem já subida
docker start nome_do_container
Para termos uma interface gráfica nos ajudando nesse processo, vamos instalar o Robo3T clicando aqui.
Conectando Banco de Dados
# Instalando o Mongoose
npm install mongoose --save
No server.js
:
// Importando Dependências
const express = require("express");
const mongoose = require("mongoose");
// Iniciando o App
const app = express();
// Iniciando o DB
mongoose.connect("mongodb://localhost:27017/nodeapi",{useNewUrlParser: true});
// Primeira Rota
app.get("/", (req,res) => {
// send() : Envio de dados genérico
res.send("Hello World");
});
// Definindo porta localhost
app.listen(3001);
Criando Models
Instalamos o require-dir para usar o require recursivamente em um diretório:
npm install require-dir --save
Em src/models/Product.js
:
const mongoose = require("mongoose");
const ProductSchema = new mongoose.Schema({
title: {
type: String,
required: true
},
description: {
type: String,
required: true
},
url: {
type: String,
required: true
},
createdAt: {
type: Date,
defaut: Date.now
}
});
mongoose.model("Product", ProductSchema);
Em server.js
:
// Importando Dependências
const express = require("express");
const mongoose = require("mongoose");
const requireDir = require("require-dir");
// Iniciando o App
const app = express();
// Iniciando o DB
mongoose.connect("mongodb://localhost:27017/nodeapi",{useNewUrlParser: true});
requireDir("./src/models");
// Primeira Rota
app.get("/", (req,res) => {
// send() : Envio de dados genérico
res.send("Hello World");
});
// Definindo porta localhost
app.listen(3001);
Estruturação e CRUD
# Hierarquia
node_modules
src
|_ models _ Product.js
|_ controllers _ ProductController.js
|_ routes.js
server.js
package.json
Em src/models/Product.js
:
// Importando mongoose
const mongoose = require("mongoose");
// Criando Esquema de Produto
const ProductSchema = new mongoose.Schema({
title: {
type: String,
required: true
},
description: {
type: String,
required: true
},
url: {
type: String,
required: true
},
createdAt: {
type: Date,
default: Date.now
}
});
// Exportando para ProductController.js
mongoose.model("Product", ProductSchema);
Em src/controllers/ProductController.js
:
// Importando o mongoose
const mongoose = require("mongoose");
// Importando o Product.js
const Product = mongoose.model("Product");
// Exportando lógicas de negócio (requisições assíncronas) para routes.js
module.exports = {
async index(req,res){
const products = await Product.find();
// Retornando json
return res.json(products);
},
async show(req, res){
const product = await Product.findById(req.params.id);
return res.json(product);
},
async store(req, res){
const product = await Product.create(req.body);
return res.json(product);
},
async update(req, res){
const product = await Product.findOneAndUpdate({_id: req.params.id}, req.body, {new: true});
return res.json(product);
},
async destroy(req, res){
await Product.findOneAndDelete({_id: req.params.id});
return res.send();
}
};
Em src/routes.js
:
// Importando o express router
const express = require("express");
const routes = express.Router();
// Importando o ProductController
const ProductController = require("./controllers/ProductController");
// Rotas
routes.get("/products", ProductController.index);
routes.get("/products/:id", ProductController.show);
routes.post("/products", ProductController.store);
routes.put("/products/:id", ProductController.update);
routes.delete("/products/:id", ProductController.destroy);
// Exportando módulo para server.js
module.exports = routes;
Em server.js
:
// Importando Dependências
const express = require("express");
const mongoose = require("mongoose");
const requireDir = require("require-dir");
// Iniciando o App e habilitando recebimento de json
const app = express();
app.use(express.json());
// Iniciando o DB com os Esquemas
mongoose.connect("mongodb://localhost:27017/nodeapi",{useNewUrlParser: true});
requireDir("./src/models");
// Prefixo e Importando routes.js
app.use("/api", require("./src/routes"));
// Definindo Porta localhost
app.listen(3001);
Testando Requisições
Podemos utilizar Front-Ends de teste como o Postman (baixando ao clicar aqui) ou o Insomnia (baixando ao clicar aqui).
Paginação de Listas
Caso tenhamos uma requisição que retorna uma lista de muitos elementos, é recomendado fazer uma paginação dessa listagem para não comprometer a performance. Primeiro instalamos o mongoose-paginate como dependência:
npm install mongoose-paginate --save
Em src/models/Product.js
:
// Importando dependências
const mongoose = require("mongoose");
const mongoosePaginate = require("mongoose-paginate");
// Criando Esquema de Produto
...
// Adicionando plugin do paginate
ProductSchema.plugin(mongoosePaginate);
...
Em src/controllers/ProductController.js
:
...
// Exportando lógicas de negócio (requisições assíncronas) para routes.js
module.exports = {
async index(req,res){
// Determinando parâmetro get (/products?page=x)
const { page = 1 } = req.query ;
// Realizando paginação
const products = await Product.paginate({}, { page , limit: 10});
// Retornando json
return res.json(products);
},
....
};
Usando o CORS
Utilizamos o CORS para permitir que outros domínios acessem nossa API. Para tal, primeiramente instalaremos o CORS como dependência:
npm install cors --save
Em server.js
:
// Importando Dependências
const express = require("express");
const mongoose = require("mongoose");
const requireDir = require("require-dir");
const cors = require("cors");
// Iniciando o App, habilitando recebimento de json e usando o cors
const app = express();
app.use(express.json());
app.use(cors());
....
Autenticação
Primeiramente instalamos o JWT como dependência:
npm install jsonwebtoken --save
Depois realizamos modificações:
# Hierarquia
node_modules
src
|_ models _ Product.js
|_ config _ auth.json
|_ controllers _ ProductController.js
|_ midlewares _ auth.js
|_ routes.js
server.js
package.json
Em src/config/auth.js
:
{
"secret": "099af4803fcb3069a85cfb3869521298"
}
Em src/controllers/ProductController.js
:
// Importando o mongoose
const mongoose = require("mongoose");
// Importando o Product.js
const Product = mongoose.model("Product");
// Importando o jwt e o secret
const jwt = require("jsonwebtoken");
const authConfig = require("../config/auth");
// Gerando Token
function generateToken(params = {}){
// Validade: 60 seg
return jwt.sign(params, authConfig.secret, {
expiresIn: 60
});
}
// Exportando lógicas de negócio (requisições assíncronas) para routes.js
module.exports = {
async index(req,res){
// Determinando parâmetro get (/products?page=x)
const { page = 1 } = req.query ;
// Realizando paginação
const products = await Product.paginate({}, { page , limit: 10});
// Retornando json
return res.json(products);
},
async show(req, res){
const product = await Product.findById(req.params.id);
return res.json(product);
},
// Gerando token ao criar produto
async store(req, res){
const product = await Product.create(req.body);
return res.send({
product: product,
token: generateToken({ id: product.id})
});
},
async update(req, res){
const product = await Product.findOneAndUpdate({_id: req.params.id}, req.body, {new: true});
return res.json(product);
},
async destroy(req, res){
await Product.findOneAndDelete({_id: req.params.id});
return res.send();
}
};
Em src/middlewares/auth.js
:
// Importando JWT
const jwt = require("jsonwebtoken");
// Importando auth.json
const authConfig = require("../config/auth.json");
// Exportando a autenticação para routes.js
module.exports = (req, res, next) => {
// Obtendo cabeçalho authorization com token
const authHeader = req.headers.authorization;
// Verificando se existe o cabeçalho authorization
if(!authHeader)
return res.status(401).send({ error: "No Token Provided" });
// Verificando se respeita o formato: Bearer token
const parts = authHeader.split(" ");
if(!parts.length === 2)
return res.status(401).send({ error: "Token error" });
const [scheme, token] = parts;
//Usando Regex para verificar se começa com Bearer
if(! /^Bearer$/i.test(scheme))
return res.status(401).send({ error: "Token Malformatted" });
//Autenticando o token com o auth.secret e verificando a validade
jwt.verify(token, authConfig.secret, (error, decoded) => {
if(error)
return res.status(401).send({ error: "Token Invalid" });
return next();
});
}
Em src/routes.js
:
// Importando o express router
const express = require("express");
const routes = express.Router();
// Importando o ProductController
const ProductController = require("./controllers/ProductController");
// Importando o auth.js
const authMiddleware = require("./middlewares/auth");
// Rotas
// A primeira rota necessita de autenticação por token
routes.get("/products", authMiddleware, ProductController.index);
routes.get("/products/:id", ProductController.show);
routes.post("/products", ProductController.store);
routes.put("/products/:id", ProductController.update);
routes.delete("/products/:id", ProductController.destroy);
// Exportando módulo para server.js
module.exports = routes;