Desenvolvendo REST APIs com NodeJS – Parte I – CodDev
Desenvolvendo REST APIs com NodeJS – Parte I
novembro 5, 2018 dezembro 19, 2018 por Paulo Bezerra, publicado em Desenvolvimento Web, JavaScript, NodeJS, Tutorial
É crescente o número de aplicações que utilizam APIs. A facilidade de criar uma aplicação cujo backend e o frontend se comunicam apenas por requisições HTTP é muito maior atualmente do que quanto tínhamos apenas a possibilidade de fazer chamadas via Ajax e manipular o DOM com JQuery.
Surgiram vários frameworks de fontend voltados para criar SPAs (Single Page Apps), que são aplicações de página única, cujo os conteúdos são trocados por javascript sem que toda a página seja renderizada. Exemplos desses frameworks são: Angular, React, VueJS, EmberJS, entre outros.
Também aumentou a necessidade de troca de informação entre um servidor de aplicação e um App de smartphone. Desta forma, a necessidade de se ter APIs independentes da apresentação do conteúdo vem se tornando cada vez mais forte.
Neste primeiro tutorial sobre o desenvolvimento de APIs, vamos explorar os passos básicos para criação de uma API: conceito de REST API, preparação do ambiente de desenvolvimento e criação de alguns serviços.
Para um próximo artigo, complementar a este, vamos abordar a parte de autenticação e segurança dos serviços. O código fonte pode ser baixado no seguinte link: https://github.com/CodDev2018/node-api
Veja aqui a parte II deste tutorial: Desenvolvendo REST APIs com NodeJS – Parte II
Se você já ouviu falar em APIs ultimamente, provavelmente deve ter ouvido falar de REST (Representational State Transfer), que nada mais é que um estilo arquitetural que define um conjunto de regras baseados em HTTP. Um web service RESTful, ou seja, implementado obedecendo a arquitetura REST, é interoperável via internet e fornece para o utilizador, acesso e possibilidade de manipulação dos recursos web por representações textuais.
Um conjunto de serviços reste para o recurso Usuários poderia ser:
Listar todos os usuários:
GET: https://dominio.com/api/usuarios
Listar um usuários:
GET: https://dominio.com/api/usuarios/1
Incluir um usuário:
POST: https://dominio.com/api/usuarios
Alterar um usuário:
PUT: https://dominio.com/api/usuarios/1
Deletar um usuário:
DELTE: https://dominio.com/api/usuarios/1
Perceba que a identificação de um recurso (URL) é sempre a mesma, o que muda são os protocolos HTTP (GET, POST, PUT e DELETE).
Crie um site profissional em apenas 24 horas.
Primeiramente para se desenvolver em NodeJS é necessário efetuar a sua instalação. Para instalar o NodeJS você pode:
Baixar o instalador do site do node de acordo com o seu sistema operacional: https://nodejs.org.
Ou instalar via o NVM (Node Version Manager): https://github.com/creationix/nvm.
O NVM é um gerenciador de versão do NodeJS, que vai facilitar muito sua vida quando necessitar utilizar diferentes versões do Node. NVM tem apenas um problema, não suporta Windows! 😥
Utilizaremos neste exemplo o MongoDB para armazenar nossos dados. O MongoDB é um banco de dados não relacional, também conhecido como banco de dados NoSQL, por não utilizar SQL para realização das suas consultas. Outra peculiaridade é que os dados ficam armazenados em esquemas semelhantes ao JSON. Para baixar o MongoDB acesse: https://www.mongodb.com.
Para escrever o código fonte você pode optar pelo Visual Studio Code, que é altamente compatível com o código ECMAScript que iremos escrever. Acesse https://code.visualstudio.com/ para baixar e instalar o VSC.
Agora com tudo instalado vamos iniciar um novo projeto. O primeiro passo é a criação de pasta, vou supor que você está utilizando ou Linux ou pelo menos um terminal compatível com o Linux como o Cmder por exemplo.
$ mkdir node-api
$ cd node-api
Com isso, será criado dentro do diretório node-api um arquivo chamado package.json. Este arquivo trás todas as configurações das dependências do nosso projeto. E por falar em dependências, vamos adicionar algumas:
Agora com as dependências instaladas o seu arquivo package.json deve ter ficado parecido com este aqui:
Vamos ao código fonte
Como deve ter visto nas dependências, acrescentamos o Express, este é o nosso framework arquitetural, ou seja é ele quem faz todo controle da execução do código, chamando o código adequado para responder cada uma das requisições recebidas pela aplicação. Ele também levanta nossa aplicação em uma determinada porta que é por onde a aplicação irá receber e responder as requisições. Para saber mais sobre o Express, e ver sua documentação completa acesse: https://expressjs.com/.
Primeiro passo é criar nosso arquivo principal da aplicação que configura todos os outros componentes utilizados vamos chamar este arquivo de server.js.
res.json('OK')
//Escutando porta
Pelo terminal você pode executar a nossa API com o momando node server.js para que a API seja servida em localhost:3000.
Algo muito chato que está acontecendo com nossa aplicação é que a cada alteração teremos que rodar novamente o comando node server.js para que a aplicação seja servida com a nova alteração. A maneira mais simples de resolver este problema, é instalando o modulo do NodeJS, o nodemon, e subir a aplicação utilizando ele. Este módulo além de servir a aplicação, faz o reload ao detectar que alguma alteração ocorreu.
Para instalar vamos dar o comando de instalação passando -g para que o modulo seja instalado globalmente, ou seja, fique acessível em qualquer diretório do sistema operacional.
Além de instalar o módulo que ira permitir executar nossa API, vamos configurar para que seja possível executar levantar nossa aplicação com o comando npm start. Para tanto, vamos acrescentar uma configuração no arquivo package.json conforme a seguir:
Com isso, bastar executar o comando npm start que nossa aplicação já será servida com o nodemon e não precisaremos reiniciar a cada alteração de código.
Crie Single Page Application com Vue.JS
Para continuar vamos criar uma estrutura de pasta para que o código fonte fique armazenado da forma mais lógica possível. Criaremos dentro do diretório raiz uma pasta com o nome de /src e dentro desta pasta mais duas outras com o nome de /src/model e /src/controller. A estrutura do projeto deve ficar assim:
Vamos começar a criar os modelos para nossa REST API. A idéia é um miniblog onde qualquer usuário cadastrado uma postagem. Os nossos modelos serão apenas um schema de usuário e outro de postagens.
Para trabalhar com os modelo precisamos de uma camadaque normalmente é dependente de um ORM (Object Relational Mappers), que é o padrão de projeto que mapeia dados de um banco de dados relacional para um modelo orientado a objeto.
Mas no caso do MongoDB, que não é um banco de dados relacional, não utilizaremos um ORM e sim um ODM (Object Document Mapper). Por ser orientado a documentos JSON sem relacionamentos e com interface de acesso sem SQL, um tradicional ORM não adiantaria nada para nós, precisamos de um mapeamento que saiba traduzir objetos em documentos do MongoDB.
Um dos ODMs mais conhecidos para MongoDB em NodeJS é certamente o Mongoose e é este que iremos utilizar. Para instalar:
Agora configuramos a conexão com o banco de dados em nossa aplicação, alterando o arquivo server.js e incluindo antes das linhas com as rotas.
mongoose.connect('mongodb://localhost/node_api', { useNewUrlParser: true });
//Rotas
Tendo o Mongoose instalado e configurado em nosso projeto podemos criar os arquivos /src/model/user.js e /src/model/post.js, a seguir os códigos fonte:
/src/model/user.js
var userScheme = new mongoose.Schema({
username: { type: String, require: true },
password: { type: String, require: true },
exports = module.exports = mongoose.model('User', userScheme);
/src/model/post.js
var postScheme = new mongoose.Schema({
titulo: { type: String, require: true },
conteudo: { type: String, require: true },
user: { type: mongoose.Schema.Types.ObjectId, ref: 'User' }
exports = module.exports = mongoose.model('Post', postScheme);
A declaração do postScheme tem uma referencia ao userSchema, mas perceba que o valor armazenado no banco de dados é do tipo ObjectId, que é a referencia única para um usuário no userSchema.
Vale ressaltar que esta referencia é apenas lógica, MongoDB não se preocupa com integridade referencial, ou outras coisas, que são implícitas de banco de dados relacionais. Desta forma ao excluir um usuário, seus posts continuariam intactos. Provavelmente dará um erro ao tentar acessar um post e pedir que o Mongoose mapeie também o user que já não existe mais no banco de dados. Então cabe a você tomar cuidado com as exclusões.
Controladores e rotas
Agora criaremos os seguintes arquivos:
/src/routes.js
/src/controller/userController.js
/src/controller/postController.js
Poderíamos criar as rotas e codificar tudo dentro do arquivo server.js como fizemos no primeiro exemplo que tínhamos a rota /test, mas imagine que um sistema cresce e pode ter centenas de rotas, ficaria inviável a manutenção. Então vamos separar nosso código de forma lógica logo no inicio para que seja mais simples mante-ló depois.
O primeiro passo é definir o arquivo de rotas e já vamos criar todas as rotas logo de início, assim já saberemos os métodos que precisaremos trabalhar em nossos controladores. Segue código do arquivo /src/routes.js:
var UserController = require('./controller/UserController')
var PostController = require('./controller/PostController')
app.get('/users', UserController.index)
app.post('/user', UserController.save)
app.get('/user/:id', UserController.get)
app.put('/user/:id', UserController.update)
app.delete('/user/:id', UserController.delete)
app.get('/user/:userId/posts', PostController.index)
app.post('/user/:userId/post', PostController.save)
app.get('/user/:userId/post/:id', PostController.get)
app.put('/user/:userId/post/:id', PostController.update)
app.delete('/user/:userId/post/:id', PostController.delete)
Por enquanto temos apenas mapeada as rotas para nossos controladores fazerem o trabalho sujo. Vamos ao primeiro controlador que é o /src/controller/userController.js.
var User = require('../model/user')
var users = await User.find().exec()
res.json(users)
async save (req, res) {
var user = await new User({
async get(req, res) {
var user = await User.findOne({ _id: req.params.id }).exec()
return res.status(404).json({ 'message': 'Usuário não encontrado' })
return res.json(user)
user.password = req.body.password
await User.findByIdAndRemove(req.params.id)
return res.status(204).json({})
exports = module.exports = new UserController()
Observe que utilizamos todos os métodos assinados com async e nas operações com banco de dados utilizamos o await, esta dupla async/await, é um recurso interessante do JavaScript, pois facilita bastante o trabalho com promises. Caso não utilizássemos este recurso, teríamos que tratar os promises passando callbacks. O problema é quando um callback fica aninhado dentro de outro callback, temos assim um código com indentação cada vez mais longe da margem esquerda, formando aquele belo "hadouken".
Basicamente o async avisa ao executor do método que o resultado depende de promises ou devolve uma promise, e o await avisa que o método que está sendo executado devolve uma promise e deve esperar este retorno para continuar executando o restante do código.
A seguir o código do arquivo /src/controller/postController.js:
var Post = require('../model/post')
async index (req, res) {
var posts = await Post.find({ user: req.param.userId }).exec()
var user = await User.findOne({ _id: req.params.userId }).exec()
var post = await new Post({
titulo: req.body.titulo,
conteudo: req.body.conteudo,
async get (req, res) {
var post = await Post.findOne({
user: req.params.userId
}).populate('user').exec()
return res.status(404).json({ 'message': 'Post não encontrado' })
async update (req, res) {
post.titulo = req.body.titulo
post.conteudo = req.body.conteudo
post = await post.save()
async delete (req, res) {
await Post.findByIdAndRemove(req.params.id)
exports = module.exports = new PostController()
Temos um bad smells relativo a repetição do código que utilizando para consultar o post. Podemos eliminar esta repetição criando um método para fazer a consulta e utilizar ele como um middleware no nível de rotas. Nosso middleware ficou assim:
async findPostMiddleware(req, res, next) {
req.post = post
next(null)
Veja que injetamos o post encontrado no objeto req. Desta forma a consulta de post ficou sendo feita em apenas um único local e não mais replicada por todo nosso código. Aproveitei e tratei o controller do usuário que também estava com um mau cheiro! Veja a seguir como ficou as duas classes:
/src/controller/postController.js:
var posts = await Post.find({ user: req.params.userId })
.populate('user').exec()
return res.status(404).json(
{ 'message': 'Usuário não encontrado' })
return res.json(req.post)
req.post.titulo = req.body.titulo
req.post.conteudo = req.body.conteudo
req.post = await req.post.save()
{ 'message': 'Post não encontrado' })
/src/controller/userController.js:
return res.json(req.user)
req.user.username = req.body.username
req.user.password = req.body.password
req.user = await req.user.save()
async findUserMiddleware(req, res, next) {
E finalmente nosso arquivo de rotas que ficou assim:
app.get('/user/:id', UserController.findUserMiddleware, UserController.get)
app.put('/user/:id', UserController.findUserMiddleware, UserController.update)
app.get('/user/:userId/post/:id', PostController.findPostMiddleware, PostController.get)
app.put('/user/:userId/post/:id', PostController.findPostMiddleware, PostController.update)
app.delete('/user/:userId/post/:id', PostController.findPostMiddleware, PostController.delete)
E para que tudo isso funcione precisamos fazer algumas alteração no server.js, que deve ficar da seguinte forma:
require('./src/routes')(app)
Neste ponto deve estar tudo funcionando, e você pode testar tudo utilizando o Postman. A seguir um print de exemplo de um teste realizado com o Postman:
Testando inclusão de usuário. Fique atendo ao marcar raw, para incluir o JSON da requisição, selecionar JSON(application/json) na caixa de seleção ao lado.
Vemos que é simples criar um web service baseado em arquitetura REST em formato JSON com NodeJS. É muito simples trabalhar com roteamentos e middlewares do Express, sem contar com a simplicidade de se trabalhar com o MongoDB com o Mongoose ODM.
Anteriormente, traduzi um post que explica detalhadamente como implantar uma aplicação Node.JS na DigitalOcean, veja: Tutorial: Como implantar seu aplicativo Node.js na DigitalOcean com SSL.
No próximo artigo iremos utilizar o Passport para fazer uma autenticação utilizando JWT (JSON Web Token), além de middlewares para validar se o usuário autenticado é o dono de fato dos dados requisitados.