Being MEVN + Restful — Parte 1

Parte 1/3 de como construir uma aplicação MEVN + RESTful API com deploy na Heroku e GitHub Pages!

Julio Lozovei
Training Center
15 min readMay 15, 2018

--

“Colorful lines of code on a MacBook screen” by Caspar Rubin on Unsplash

Na introdução do nosso tutorial vimos alguns conceitos básicos sobre WebServices, RESTful APIs e MEAN/MERN/MEVN. Agora, começar a codificar o back-end do nosso projeto.

1 — Criando e clonando o repositório

Primeiro de tudo, iremos criar um repositório no GitHub — você pode colocar o nome que quiser, mas é sempre bom utilizarmos um nome que indique o tema ou o quais tipos de código ele irá armazenar. Após criar o seu repo, você deverá cloná-lo em o seu HD.

Se você é novo com o GitHub e não faz ideia de como se cria e clona um repositório, siga esses passos:

1.1 — Criando o repositório

  • Após criar a sua conta, ou fazer login, vá até o canto direito superior no header e clique no ícone +, e depois em New repository
Localização do link para criar um novo repositório no Github
  • Insira um nome memorável, que indique o conteúdo do seu repositório
Inserindo um nome para o seu repositório
  • Opcionalmente, você poderá inserir também uma descrição
Inserindo uma descrição para o seu repositório
  • Configure se esse novo repositório será público ou privado — para você poder criar repositórios privados, você assinar um plano do GitHub
Inserindo a privacidade do seu repositório
  • Você também pode, no momento da criação do repositório, opcionalmente adicionar um arquivo de leitura, o README.md, e configurar a licença do seu projeto e também inserir o .gitignore — onde ficarão os file patterns que o git deverá ignorar
Inserindo arquivos padrão no seu repositório

1.2 — Clonando o repositório

Você pode utilizar 3 métodos para clonar o repositório:

  • Baixá-lo na sua máquina em um arquivo .zip
  • Clonar utilizando um terminal
  • Clonar utilizando o GitHub Pages

1.2.1 — Baixando .zip

Você verá um botão verde (bem chamativo) dentro do seu repositório. Clique nele e, no dropdown que aparecerá após o clique, selecione a opção Download zip. O download iniciará automaticamente.

Botão para clonar ou baixar o repositório
Opções para clonar o repositório

1.2.2 — Clonando utilizando um terminal

Para isso, você precisa ter instalado o git na sua máquina e ter a url do seu repositório. Na imagem ali de cima vimos que, quando clicamos no botão Clone or download, uma url é gerada. Você pode copiar essa url e, no terminal da sua máquina, você irá inserir o seguinte comando:

git clone https://github.com/YOUR-USERNAME/YOUR-REPOSITORY

Pressione enter e o processo de clonagem irá inciar!

1.2.3 — Clonando utilizando o GitHub Pages

Da mesma maneira como na primeira opção, clique no botão Clone or download e selecione a opção Open in Desktop. Se você já tiver instalado o GitHub Desktop na sua máquina, ele irá automaticamente clonar o repositório e mostrar visualmente dentro do software.

Esses fluxos, e outras coisas bem bacanas, estão disponíveis na sessão de ajuda do GitHub.

2 — Iniciando o projeto com npm

Nós vamos utilizar o npm (node package manager) como gerenciador de pacotes. O npm é um serviço grátis que já vem instalado com o Node e possui, basicamente, 2 funções — ele é um repositório online para projetos públicos feitos em javascript (utilizando Node) e também uma ferramenta de linha de comando que interage com esses repositórios. No npm, chamamos esses repositórios de packages ou modules. Pelo npm, você pode gerenciar os pacotes que o seu projeto irá utilizar.

Existem outros gerenciadores de pacotes , como o Yarn e o Bower (esse, destinado para packages utilizados apenas no front-end); você pode utilizar qualquer um (yarn / npm), no final não muda muita coisa.

Enfim, vamos codar!

Para criar o projeto utilizando o npm, vá até a pasta do repositório clonado e, no seu bash (terminal), insira o comando:

npm init

Esse comando irá notificar o npm que um novo projeto será criado, e ele irá fazer uma série de perguntas para gerar o package.json. Esse arquivo contém todas as informações pertinentes sobre o seu projeto — nome, versão, autor, repositório git… Nele também ficam armazenadas as dependências que seu projeto usa — os packages do npm.

Dentro do package.json também existe uma sessão chamada scripts. Nesses scripts ficarão armazenados os atalhos para comandos que iremos executar em dados momentos, para que a nossa aplicação inicie ou para que os testes sejam executados, por exemplo.

Se essa parte dos scripts ficou meio confusa, não se preocupe; ao longo do tutorial iremos falar mais sobre isso.

3 — Instalando as dependências necessárias

Para criar o código do nosso server-side com uma RESTful API, iremos utilizar alguns pacotes que irão nos ajudar. Entre eles, estão: express, body-parser nodemon, config e debug.

Para instalar essas dependências, você deverá utilizar o comando:

npm install <nome_do_pacote> --save 

Alguns pontos a serem observados:

  • npm install é bem sugestivo, irá dizer ao npm que você irá instalar um ou mais pacotes (você pode instalar um por vez ou todos em uma vez só);
  • <nome_do_pacote> deverá ser substituído pelo(s) nome(s) do(s) pacote(s) — por exemplo, express
  • a flag --save indica que iremos salvar esse(s) pacote(s) como dependência(s) do nosso projeto. Depois iremos falar mais sobre os tipos de dependência.

Então, para installar os pacotes que citamos, iremos utilizar o comando:

npm install express body-parser nodemon config debug --save 

Depois de instalar todos os pacotes, você irá notar que o npm criou uma pasta na raiz do seu projeto, node_modules. Essa pasta contém todos os source codes dos pacotes — é super importante que você não mexa nessa pasta, porque se você mexer, coisas ruins irão acontecer no seu projeto!

O Sublime Text tem uma funcionalidade muito bacana que permite excluir arquivos ou pastas dos resultados de busca e da própria visualização no projeto. Eu utilizo essa funcionalidade para excluir todo o conteúdo da node_modules das minhas buscas e também deixá-la invisível. Para fazer isso, você deve acessar a seguinte ordem de menus do Sublime:

Preferences -> Settings — User

Em seguida irá aparecer na sua tela um arquivo com todas as configurações do Sublime, em formato JSON. Copie e cole o código a seguir para "esconder" a node_modules e outros arquivos de configuração do seu projeto:

"escondendo" node_modules (e outras pastas/arquivos) do Sublime Text

Nesse arquivo, você também pode configurar toda a aparência do Sublime, como tamanho da fonte, tema, largura padrão do tab

4 — Criando estrutura base

Após a instalação dos pacotes, agora vamos definir a estrutura base da nossa API: iremos utilizar o Express para gerenciar as rotas da nossa aplicação e utilizaremos o padrão MVC (Model View Controller) de arquitetura para o server-side. Como iremos criar o nosso client-side em um ambiente separado, na verdade iremos utilizar o MVC sem o V (piada interna).

Para isso, você deverá criar um arquivo na raiz do projeto, app.js. Ele será o principal arquivo da nossa API.

4.1 — app.js

O principal arquivo do server-side, é através dele que tudo irá começar e funcionar. Nesse arquivo iremos configurar todo o nosso servidor, como por exemplo, nossas rotas, nossos middlewares

app.js

Temos 5 pontos importantes para serem observados aqui:

  • importando os packages
    Nessa fase estamos importanto os pacotes do npm que iremos utilizar para que o nosso servidor seja criado;
  • parsers + CORS
    Iremos utilizar o body-parser para que todas as informações das requisições sejam convertidas para JSON. Também estamos fazendo a configuração do CORS, para que aplicações que estão em outro domínio consigam utilizar os nossos endpoints;
  • configurando a porta de incialização
    Aqui configuramos onde a nossa aplicação estará disponível — poderíamos colocar um valor fixo, como 3001, porém se essa porta já estiver ocupada nossa aplicação não irá funcionar. Utilizando process.env.PORT como condicional, estamos salvaguardando que nossa aplicação será inicializada em alguma porta — ou a 3001, ou qualquer outra que estiver disponível;
  • configurando as rotas
    Através das rotas que nossos dados serão fornecidos — para deixar tudo mais organizado, iremos colocar todas as rotas em um lugar separado da configuração do nosso servidor;
  • exportando o módulo
    Um módulo encapsula um grupo relacionado de código em uma únida unidade — dessa forma, estamos agrupando toda a configuração do nosso servidor e exportando para que o node consiga interpretar.

4.2 — Nodemon

Para automatizar o processo de reinicialização da nossa aplicação, iremos utilizar o nodemon.

Ele ficará ouvindo todas as alterações realizadas em nossos arquivos e, quando houver uma mudança (quando salvarmos um arquivo), ele irá reiniciar o processo do node no qual nossa aplicação estará rodando.

No começo do projeto nós fizemos a instalação desse package; e para realmente utilizá-lo, precisamos colocar a instrução no package.json, dessa forma:

sessão "scripts" do package.json

Dessa forma, estamos utilizando os scripts do npm para dizer que iremos utilizar o nodemon como watcher do nosso servidor. Agora, no terminal, quando o comando npm start for inserido, ele irá executar o script start e assim o nodemon irá ficar escutando nossos arquivos.

Mas, espera um pouco! O nodemon não deveria estar escutando o app.js? Na teoria, sim. Porém, para deixar tudo modularizado, vamos separar a inicialização da nosso app — na raiz do projeto iremos criar um novo diretório, /bin/, e dentro deles iremos criar um arquivo novo, www — isso mesmo, sem extensão.

O arquivo /bin/www deverá ficar assim:

código do /bin/www

No arquivo app.js nós definimos a porta onde os arquivos serão servidos — estamos importando ela nesse nosso arquivo e criando o nosso server listener que será escutado pelo nodemon.

5 — Roteamento

De uma maneira bem genérica, a definição das rotas com o Express é feita dessa forma:

estrutura básica de roteamento com Express

Onde:

  • definimos o objeto Router() do Express
  • informamos o método da request como uma função: router.get ou router.post
  • informamos o endpoint que receberá a request: router.get('/path')
    Você pode passar parâmetros para a sua rota, como um id por exemplo. Para isso, use a seguinte convenção: router.get('/:_id')
  • definimos a função para o envio da resposta com os parâmetros request (a própria requisição) e response(o objeto que será enviado): router.get('/', (request, response) => { ... })

Nós também podemos definir funções de middleware em cada rota. Isso é muito utilizado para validações de token ou headers, por exemplo.

5.1 — /routes/

Como declaramos no app.js, todas as rotas estarão dentro do pasta /routes/. Utilizaremos esse padrão para manter nosso projeto organizado — poderíamos colocar todo o código dentro do app.js, porém dessa forma iríamos sofrer com a manutenção.

Iremos utilizar o objeto Router do Express para controlar o nosso roteamento.

Dentro do nosso diretório /routes/ vamos criar o arquivo index.js:

configurando rota /publications/

Para manter uma arquitetura mais limpa com maior nível de organização, definimos um grupo de rotas — publications. Ou seja, o arquivo /publications/index.js que irá receber e administrar todas as requisições que chegarem até esse endpoint.

Para criarmos nosso CRUD, as requests serão esperadas nesse caminho e também com o método certo. Dentro do protocolo HTTP temos vários métodos, e iremos utilizar os métodos GET, POST, PUT e DELETE para fazer o trânsito das informações na nossa API.

O código do /publications/index.js ficará dessa forma:

configurando middleware de autenticação nas rotas

Cada rota irá chamar um método da nossa controller, que irá receber os dados da requisição e processá-los para enviar ao o model. O model, por sua vez, irá receber os dados da controller e fazer a persistência para o banco de dados.

Em cada rota, também colocamos uma função intermediária, também chamada de middleware. Nesse caso, o middleware irá autenticar as nossas requisições.

função de autenticação — /routes/auth/index.js

5.2 — Autenticação

Para proteger a API, existem 2 métodos muito utilizados: tokens e cors. Esses dois métodos podem ser combinados para gerar ainda mais camadas de segurança na sua aplicação. Para o nosso projeto eu utilizei a validação de token através do header das requisições e o cors, porém de maneira bem simples para demonstrar como funciona (até porque os dados gerados pelo nosso projeto não possuem grande sensibilidade).

Basicamente, com os tokens, você irá adicionar um sinaleiro em suas requests. Esse sinaleiro terá uma data de validade e, quando essa data passar, o token será inválido — isso é muito utilizado para o controle de sessão de usuários.

Por exemplo, quando o usuário faz o login, sua aplicação irá validar os dados inseridos pelo usuário com o registro do banco de dados e, se estiver correto, o usuário poderá entrar. Nesse momento, depois da autenticação dos dados e antes do usuário entrar na sua conta, a aplicação gera um token e devolve ele para o usuário. Nas próximas requisições, ao invés de passar sempre o user/pass, você irá passar o token. Se esse token for válido, o usuário poderá navegar pela aplicação — quando ele for inválido, você poderá terminar a sessão desse usuário e pedir uma nova autenticação com user/pass. Para isso, existe um package maravilhoso no npm, o jsonwebtoken.

Na restrição de hosts, você irá restringir o acesso da sua API para determinados hosts — ou seja, nem todo mundo na web poderá acessar seus endpoints e ter uma resposta. De forma simples, você irá dizer para a sua aplicação quais são os hosts conhecidos que poderão obter respostas dos seus endpoints.

6 — MongoDB

MongoDB é um banco de dados orientado a documentos — espera, o que isso quer dizer?

O Mongo não é um banco como o SQL Server, MySQL e afins… Estamos falando de um banco de dados NoSQL — ele não aceita comandos SQL, não possui o conceito de tabelas, esquemas, linhas; não tem transações, joins, primary e foreign keys, tampouco procedures.

Ele foi criado para ser escalável e performático, capaz de aguentar uma gigantesca quantidade de dados e consultas.

No lugar das tabelas e linhas, nós temos coleções e documentos, respectivamente. Sem a necessidade de transações ou ACID, nós simplesmente confiamos que a escrita será feita na base de dados de alguma forma. Para isso, uma arquitetura ideal de Mongo deve conter três instâncias, no esquema réplica set (master/slave).

Dentro do Mongo também não há a necessidade de criar um database — ele será criado automaticamente quando você utilizá-lo pela primeira vez. As collections também funcionam da mesma maneira — elas serão criadas quando o primeiro registro for salvo na base.

6.1 — Sintaxe

Dentro do Mongo não existe uma interface gráfica, como no MySQL por exemplo. Para realizar as consultas e visualizar os resultados, tudo é feito pelo terminal. O terminal do Mongo é um console JavaScript, assim como o console do Node.

Então, todos os comandos, de maneira básica, possuem uma sintaxe de código JavaScript. Para criar/trocar de database, utilize o comando:

use <db_name>

E substitua <db_name> pelo nome do seu database.

Quando você está dentro do seu database, o comando db irá funcionar como um ponteiro (quase como um this) que aponta para o próprio database. Para realizarmos as operações (busca, inserção, alteração e deleção), utilizamos o seguinte padrão de comando:

db.<colletion_name>.<operation>

Simples, não é?

Ou seja, para inserir um registro, iremos utilizar o comando:

db.example.insert({ name: 'Fancy User', gender: 'user_gender', age: 32, hometown: 'Fancy City' })

Como mencionado há alguns parágrafos acima, se esse for o seu primeiro registro, a sua coleção será criada automaticamente após esse registro ser gravado no Mongo.

Outro ponto interessante para mencionar, as chaves identificadoras do Mongo (podemos chamar de primary keys apenas para ficar mais fácil a assimilação, pois o Mongo não possui esse conceito) são criadas por uma função interna do próprio banco que garante que ela será única. Em cada novo registro adicionado, é criado um campo, _id. O valor desse campo fica armazenado em uma collection interna, a system.indexes.

No comando insert você pode adicionar um registro, em forma de um objeto javascript, ou vários registros inseridos em um array de objetos.

Para não ficar muito extenso, irei deixar aqui um gist com alguns exemplos de comandos do Mongo:

comandos úteis e básicos do Mongo

6.2 — Conexão com API

Bom, agora que habemus banco, precisamos conectá-lo a nossa API para que as informações possam ser persistidas. Para fazer a conexão do Mongo com o Node, existem vários packages no npm. Nesse projeto iremos utilizar o mongojs pela sua simplicidade.

Se nossa aplicação fosse lidar com muitas collections ou tipos de entidade (usuários, grupos, publicações), eu recomendaria o uso do mongoose.

Para conectarmos a nossa API com o Mongo, iremos criar agora 2 novos arquivos:

  • /config/default.json
  • /db/mongo.js

O arquivo /config/default.json irá armazenar as keys e values da nossa conexão. E o arquivo /db/mongo.js irá armazenar a nossa string de conexão.

Sendo assim, o arquivo default.json ficará dessa forma:

configurações padrão do projeto — /config/default.json

E o mongo.js dessa:

conectando nossa API com o MongoDB — /db/mongo.js

Vale ressaltar aqui que, para acessarmos os valores do default.json, estamos utilizando o package config. Com esse package, podemos salvar em formato JSON vários encadeamentos de chave-valor e acessarmos em outros lugares do projeto.

Dessa forma, estamos centralizando a informação que interessa em apenas um lugar — quando precisarmos alterar qualquer uma dessas chaves ou valores, iremos alterar apenas no config.json, e não no projeto inteiro.

7 — Controllers + Models

Como dito anteriormente, estamos utilizando a arquitetura MVC (sem o V) para criarmos a nossa API.

O principal foco nesse padrão de arquitetura são as Controllers, que se comunicam diretamente com os Models e com as Views. Explicar com desenhos é mais fácil do que explicar com palavras, então, esse é um diagrama bem básico de como funciona a arquitetura:

Fluxo Básico do MVC

7.1 — Controllers

A função das Controllers é receber as informações da View e enviar para as Models. O recebimento dessas informações é feito através das rotas — em cada rota, chamamos uma função de uma controller, passando como parâmetro os dados da requisição que chegou até a rota.

A resposta da controller para a view (ou para a API) será a resposta da operação da Model — se os registros foram ou não gravados, e o motivo.

No nosso projeto teremos apenas 1 controller, a PublicationController.js, que receberá os dados das requisições feitas para a rota /publications/ da nossa API.

Iremos deixar a controller dentro da pasta /controllers/, ficando o código dessa maneira:

/controllers/PublicationController.js

7.2 — Models

Já as Models fazem a comunicação direta com o banco de dados. Elas recebem os dados das funções das controllers e realizam a persistência para a base de dados; e retornam para a controller o status da operação — se os registros foram ou não gravados, e o motivo.

Da mesma forma que as controllers, iremos ter uma model, PublicationModel.js, que irá receber os dados das funções da nossa PublicationController.js.

Iremos deixar a nossa model dentro da pasta /models/, e o código dela ficará dessa maneira:

/models/PublicationModel.js

8 — Testes

Uma das últimas fases durante a construção de vários sistemas, porém não a menos importante, são os testes. Para o cenário do desenvolvimento de APIs, os testes que devemos fazer são para as rotas e requisições — apontar para o endpoint correto e verificar se o resultado obtido é coerente com o esperado.

Para realizar os testes da nossa API iremos utilizar o mocha e o chai. O mocha irá controlar os nossos testes, e iremos utilizar o chai para realizar as requests para os nossos endpoints.

Para iniciarmos a fase de testes, precisamos então instalar os packages de testes. Iremos rodar o comando:

npm install mocha chai --save-dev

Diferente de todos os packages que instalamos, para esses 2 colocamos a flag --save-dev — isso irá indicar que essas dependências estarão apenas disponíveis no ambiente de desenvolvimento, ou seja, não iremos depender delas para que a nossa aplicação funcione no ambiente de produção.

Isso explica bem a questão dos tipos de dependências, que falamos um pouco mais acima — processadores e transpiladores de código, packages de teste geralmente são instalados utilizando a flag --save-dev.

Já a grande maioria dos outros packages — com o exemplo do nosso projeto, express, mongojs, config e os outros — são instalados normalmente com a flag --save.

Vamos primeiro mostrar o código e depois explicar:

testando nossos endpoints — /tests/unit/index.js

De forma bem simples, para escrevermos os testes utilizamos o describe para descrever cada suíte e o it() para descrever cada unidade. É muito importante lembrar de que, quando chegamos ao final de cada teste (it), precisamos chamar o método done() para terminar aquele teste específico — quando esquecemos esse método, o framework que estamos utilizando irá ficar esperando mais comandos e, provavelmente, nosso teste irá falhar por conta do timeout.

Para realizar os testes em nossos endpoints, iremos realizar consultas utilizando os 4 verbos: buscar todos os registros (GET), buscar um registro específico (GET), criar um novo registro (POST), editar um registro (PUT) e deletar um registro (DELETE).

Você também pode utilizar outros frameworks de testes específicos para APIs, como o Swagger UI ou até mesmo os Collection Runners do Postman.

9 — Conclusão

UFA! Chegamos ao final da primeira parte do tutorial. Se você chegou até aqui ileso, meus parabéns!

O código desse projeto estará disponível nesse repositório que criei especialmente para o tutorial.

Na próxima parte, iremos construir o front-end da nossa aplicação — que irá consultar e consumir os dados da nossa API. Os códigos que foram mostrados aqui na publicação também estarão salvos (perdidos) nos meus Gists.

Construindo Aplicações com NodeJS — William Bruno, Novatec

Eu aprendi essa abordagem de criação de RESTful APIs em Node com o livro Construindo Aplicações com NodeJS, do William Bruno.

No final de 2016, tive a oportunidade de participar de um curso de Node ministrado pelo William Bruno, no CT da Novatec. Lá ele nos ensinou o passo-a-passo de como criar uma RESTful API sólida, porém de forma muito simples.

Se você ficou interessado pelo livro, pode encontrá-lo aqui:
https://novatec.com.br/livros/nodejs

--

--

Julio Lozovei
Training Center

Human, front-end developer, amateur musician, writer and speaker; problem solver. https://jlozovei.dev