';

Olá, pessoal!

Hoje eu quero mostrar o passo-a-passo pra você entender e aprender como configurar um projeto usando diversas coisas ao mesmo tempo, como Docker, Node, Express, Mongo e Babel.

Escopo do Projeto

O escopo do projeto é bem simples, será uma API escrita em Node.js usando ES6 e para servir essa API usaremos o express.

A API terá apenas duas rotas, a / que lista as contas criadas e a /create que cria as contas aleatoriamente usando a biblioteca faker.

As contas serão armazenada em um banco de dados Mongo e iremos usar o MongoClient do driver mongodb para fazer as operações.

Tudo isso estará dentro de uma arquitetura usando Docker, e teremos três containers dentro do Docker, um será o nosso container web, que utiliza a imagem node:alpine, o outro container será o nosso mongodb e por fim teremos também um container com o adminmongo, para que possamos facilmente visualizar os conteúdos do nosso banco.

Usaremos o docker-compose para criar esses serviços pra gente.

Pré-requisitos

Para esse projeto, você precisa ter o docker instalado em seu computador, basta você baixar o docker para o seu sistema operacional e instalá-lo.

Precisaremos também que você tenha o Node.js instalado em sua máquina.

Algums lembretes importantes:

– No terminal, quando o docker estiver rodando, para pará-lo você pode usar o CTRL + C.

– No Browser, eu sempre falo localhost:PORTA, lembre que tem também o http:// antes (que o browser auto-completa).

RECOMENDO que você siga esse tutorial passo-a-passo, executando cada passo em seu computador.

Eu sei que a maioria das pessoas não vão ler o texto todo, mas os que lerem o texto todo eu sei que estão realmente interessados em aprender o que eu estou mostrando aqui.

Criando a infraestrutura

Vou começar criando uma pasta chamada random-users e vou entrar nela.

Agora vamos começar inicializando um package.json usando o commando npm init -y.

Agora vamos criar um Dockerfile, o arquivo deve se chamar Dockerfile e nele vamos colocar o seguinte conteúdo.

FROM node:alpine

WORKDIR /usr/app

COPY package*.json ./

RUN npm install

COPY . .

EXPOSE 3000

CMD ['npm', 'index.js']

Esse conteúdo é como se fosse a receita para que o docker possa criar o nosso container, deixa eu explicar passo a passo o que foi feito aqui.

Começamos com o comando FROM indicando qual imagem iremos usar como base e nesse caso temos o node alpine.

Em seguida temos o WORKDIR indicando qual será o path ou seja a pasta dentro do docker que nós iremos trabalhar.

No comando COPY estamos copiando os arquivos package*.json para o nosso WORKDIR (ou seja o /usr/app).

O * (asterísco) entre o package e o .json é pra indicar que queremos que ele copie os arquivos que começarem com package e terminarem com .json, ou seja vai pegar tanto o package.json quanto o package-lock.json.

Agora rodamos o RUN, falando com o docker para instalar as dependências do nosso projeto usando o commando npm install.

Nesse segundo copy, estamos copiando tudo da nossa pasta atual para dentro do docker na WORKDIR.

E por ultimo executamos o nosso comando de inicialização da aplicação, que no caso é o npm index.js.

Repare que o comando é um array e por mais estranho que pareça é assim mesmo que devemos utilizar.

Agora vamos criar um arquivo chamado .dockerignore e nesse arquivo vamos deixar apenas node_modules.

Estamos fazendo isso pois quando o docker for inicializar e copiar tudo para dentro do container, ele não precisa de copiar a pasta node_modules, pois ela será criada internamente quando o próprio docker rodar o comando npm install.

Instalando Express

Agora vamos fazer o seguinte, vamos instalar o express e fazer uma configuração básica, pois quando a gente rodar o docker para criar tudo, queremos ter algum serviço rodando lá dentro.

O primeiro passo é usar o npm para instalar o express, fazemos isso com o comando `npm install –save express`.

npm install --save express

npm install –save express

Isso irá criar a pasta node_modules e também o package-lock.json.

Vamos inicializar nossa aplicação em express criando um arquivo chamado index.js e colocar o seguinte conteúdo.

var express = require('express');

var PORT =  3000;

var app = express();

app.use('/', function(req, res) {
  res.send('EmersonBrogaDev');
});

app.listen(PORT, function() {
  console.log('Running on port ' + PORT);
});

No começo do arquivo index.js importamos o express.

Em seguida definimos a PORT(a) que ele vai rodar, nesse caso porta 3000, que é a mesma porta que definimos no EXPOSE do nosso Dockerfile.

Depois criamos uma instância do express e atribuímos à variável app.

Criamos a nossa rota padrão usando app.use que irá retornar a string ‘EmersonBrogaDev’;

E por fim falamos em qual porta ele irá escutar as requisições.

Nesse ponto sua aplicação deve estar assim:

index.js

index.js

Rodando o projeto

Agora vamos rodar esse projeto.

Como acabamos de criar o nosso index.js, vamos colocar em nosso package.json um comando para que ele inicialize nossa aplicação usando o index.js, então abra o package.json e dentro dele adicione a seguinte linha dentro da chave “scripts”.

"start": "node index.js"

O seu package.json agora deve estar assim:

Package.json

Package.json

Vamos recapitular o que fizemos até aqui.

Inicializamos o package.json com npm init, criamos o nosso Dockerfile com as instruções pro docker criar o container e criamos a nossa pequena aplicação usando express.

Agora vamos colocar isso tudo pra funcionar.

Primeiro vamos falar pro docker criar o nosso container e vamos dar um nome a ele, então digite no terminal:

docker build --tag=emersonbroga/random-users ./
Docker Build

Docker Build

Com isso o docker irá criar o nosso container e dar a ele o nome de “emersonbroga/random-users”.

Agora vamos rodar essa nossa aplicação usando:

docker run -p 3000:3000  emersonbroga/random-users
Docker Run

Docker Run

Agora se você acessar a aplicação no browser em localhost:3000 você deverá ver a mensagem que foi definida em nosso index.js.

localhost:3000

localhost:3000

Definido Volumes com docker-compose

Pronto temos nossa aplicação rodando, agora vamos mudar a mensagem, para: “Que tutorial massa do Emerson Broga!”

altere a mensagem no index.js de:

res.send('EmersonBrogaDev');

para:

res.send('Que tutorial massa!');

e volte ao browser e atualize a página.

E pra sua decepção ainda está mostrando EmersonBrogaDev.

Isso acontece porque o seu arquivo index.js não está atualizado dentro do container.

Para atualizar você precisa de criar novamente o container e inicializá-lo, então rode os comandos:

docker build --tag=emersonbroga/random-users ./
docker run -p 3000:3000  emersonbroga/random-users

E agora vá ao browser e atualize a página e você deverá ver a mensagem esperada.

Porém, desenvolver um projeto tendo que rodar o docker build e docker run toda hora parece não ser a forma mais produtiva, precisamos de uma forma de fazer com que isso nosso código fonte atualize de forma automática dentro do container do docker.

Para isso precisamos de usar o Docker Compose, que é um orquestrador que irá definir como os containers devem se comportar dentro da nossa aplicação e mais algumas outras funcionalidades.

Uma das coisas que precisamos de usar do docker compose são os volumes. Com o volumes a gente consegue mapear uma pasta do nosso computador para ser espelhada dentro do nosso container.

Então vamos criar o nosso arquivo docker-compose.yaml, isso mesmo, o formato do arquivo é yaml (leia mais sobre esse formato aqui).

version: "3"
services:
  web:
    build: .
    command: npm start
    volumes:
      - .:/usr/app
    ports:
      - "3000:3000"

No docker-composer.yaml, definimos a versão, nesse caso a versão 3 e em seguida definimos nossos serviços.
O primeiro e por enquanto único serviço é o serviço que eu chamei de web.
O build dele é na pasta atual, que contem o nosso Dockerfile com as instruções de como criar o container.
O comando (command) para iniciar a a aplicação é o npm start.
Nos volumes, estamos mapeando a pasta atual para a pasta WORKDIR do nosso Dockerfile que no caso é a /usr/app
E por fim definimos o mapa de portas, mapeando a porta 3000 do nosso computador para a porta 3000 do container.

Com isso concluímos a configuração do nosso docker-compose.

Agora que temos temo os volumes mapeados, precisamos demais um pequeno passo que é instalar o nodemon, para que ele “escute” as alterações dos arquivos e reinicie automaticamente nossa aplicação node automaticamente.

Volte ao terminal e instale o nodemon usando npm install --save nodemon.

Agora vá ao package.json e mude o comando start de:

"start": "node index.js"

Para:

"start": "nodemon index.js"

Agora vamos rodar tudo isso e usar o docker-compose, vá até o terminal e rode o seguinte comando:

docker-compose up

Vá ao browser e visite localhost:3000 e você verá “Que tutorial massa!”, até aqui tudo correto.

Agora abra o index.js e altere a mensagem para “Agora com Docker compose e nodemon!”, salve o arquivo e atualize o browser.

Se você viu “Agora com Docker compose e nodemon!”, pode comemorar, pois agora uma vez que inicializamos a aplicação podemos alterar os arquivos e tudo será automaticamente atualizado.

Instalando o MongoDB em nosso projeto

Agora que temos a nossa estrutura base pronta para começarmos o desenvolvimento de verdade, então vamos instalar o mongodb.

Vamos voltar ao nosso docker compose e adicionar mais um serviço, o db.
Dentro de services adicione:

  db:
    image: mongo
    volumes:
      - ./data:/data/db
    ports:
      - "27017:27017"

Com isso estamos falando pro docker compose, usar a imagem oficial do mongo para criar o nosso container que irá ter como volumes a pasta data do nosso diretório atual mapeada para /data/db dentro do container e que vamos mapear a porta 27017 da nossa máquina para a porta 27017 do container.

Vamos também colocar no nosso serviço web, que ele depende do serviço db, usando o depends_on.

depends_on:
  - db 

Com isso seu docker-composer.yaml deve estar assim:

docker-compose

docker-compose

Não se esqueça de criar a pasta data na raiz do seu projeto (na mesma pasta que temos o docker-composer.yaml).

Conectando ao mongodb com mongo client

Primeiramente vamos instalar o driver mongo do nodejs para que possamos utilizar o mongo client.

Vá no terminal e digite:

npm install --save mongodb

No nosso projeto, vamos começar a organizar nossa aplicação, criando uma pasta chamada src.

Dentro da pasta src, vamos criar uma pasta chamada database.

Dentro da pasta database, vamos criar um arquivo chamado Mongo.js.

Nesse arquivo Mongo.js vamos colocar o seguinte código:

var mongo = require('mongodb').MongoClient

var DATABASE = 'application';
var MONGODB_URI = 'mongodb://db:27017';

module.exports = function getDatabase(callback) {
  var dbOptions = {
    useNewUrlParser: true,
  };
  mongo.connect(MONGODB_URI, dbOptions, function(err, client) {
    if (err) return callback(err);
    
    var db = client.db(DATABASE);
    callback(null, db);
  });
};

Aqui a gente importa o MonogoClient da biblioteca mongo para a variável mongo.
Em seguida, criamos uma variável para o nome do nosso banco de dados, que nesse caso eu chamei de “application”.
Depois definimos a string de conexão (MONGODB_URI) do mongo, que no caso é mongodb://db:27017, ou seja, temos o protocolo mongodb, o host db, que é o nome do nosso serviço de banco definido no docker-compose.yaml que é automaticamente resolvido para o ip interno do container usar no o serviço de dns interno do docker e por fim a porta, que definimos em nosso docker-compose.yaml.

Depois temos exportamos desse arquivo uma função chamada getDatabase() que recebe como parâmetro um callback.
Dentro dessa getDatabase, definimos as opções do nosso db na variável dbOptions e passamos o useNewUrlParser como true, para que ele possa entender nossa string de conexão.

Na sequência chamamos o mongo.connect passando a string de conexão (MONGODB_URI), os options (dbOptions) e um callback que receberá um erro (err) ou o client.
Se tiver algum erro, retornamos o erro para o nosso callback.
Se o client estiver disponível, pegamos a DATABASE e atribuímos à variável db.
retornamos a db para o callback, utilizando o padrão de callback onde primeiramente enviamos o erro, null nesse caso e no segundo parâmetro enviamos o result que nesse caso é o nosso db.

Agora vamos no nosso index.js e vamos adicionar alguma coisa no banco quando o usuário visitar a página inicial (‘/’).

O código final do seu index.js deve ser o sequinte:

var express = require('express');
var getDatabase = require('./src/database/Mongo');
var PORT =  3000;
var HOST = '0.0.0.0';

var app = express();

app.use('/', function(req, res) {

  getDatabase(function(dbErr, db) {
    if (dbErr) return res.json({error: dbErr});

    var collection = db.collection('accounts');

    collection.insertOne({name: 'Emerson Broga'}, (insertErr, result) => {
     if (insertErr) return res.json({error: insertErr});
     
     return res.json({data: result.ops[0]});
    });
  });
});

app.listen(PORT, function() {
  console.log('Running on port ' + PORT);
});

Logo depois do require do express colocamos um require pra nossa função getDatabase.

Dentro do callback da nossa app.use('/'), chamamos a função getDatabase e passamos o callback.
Esse callback recebe os seguintes parâmetros dbErr e o db.
Se tiver algum dbErr, retornamos a requisição com um json passando o erro.

Se o db estiver disponível, buscamos o collection ‘accounts’.

Com o collection accounts em mãos, vamos inserir um registro no banco, usando o collection.insertOne.
No primeiro parâmetro, passamos o objeto a ser inserido, que nesse caso é {name: 'Emerson Broga'}.
No segundo parâmetro, temos o callback que recebe o insertErr e o result.
Se tiver algum insertErr, retornamos a requisição com um json passando o erro.

Se a inserção ocorrer com sucesso teremos um result.
Desse result vamos retornar o item criado, que está no result em ops na chave 0 (result.ops[0]);

Nesse ponto, você deve ter a seguinte estrutura:

 Estrutura do projeto

Estrutura do projeto

Agora vá ao terminal e execute esse comando para criar o nosso container do mongo e aplicar todas alterações que fizemos:

docker-compose up --build

Se tudo ocorrer bem, ao visitar o localhost:3000 no browser você deverá ver o registro que foi adicionado no banco com o id gerado pelo mongo.

localhost:3000

localhost:3000

Escrevendo JavaScript Moderno (ES6+) no Node com Babel

Agora vamos instalar o babel ao nosso projeto para que possamos escrever um JavaScript mais moderno em nossa aplicação.

Vamo começar adicionando a biblioteca do Babel utilizando o npm:

npm install --save-dev @babel/core @babel/node @babel/preset-env

Agora vamos criar na raiz do projeto um arquivo chamado .babelrc com o seguinte conteúdo:

{
  "presets": [
    "@babel/preset-env"
  ]
}

Agora vamos voltar ao nosso package.json e alterar novamente o nosso script start.

Vamos mudar de:

"start": "nodemon index.js"

para:

"start": "nodemon --exec babel-node index.js",

Para garantir que o babel esta funcionando corretamente, vamos mudar o require para import.

No index.js altere o seguinte trecho, de:

var express = require('express');
var getDatabase = require('./src/database/Mongo');

para:

import express from 'express';
import getDatabase from './src/database/Mongo';

Agora vamos atualizar nosso container pra ver se tudo continua como antes.

docker-compose build
docker-compose up

Refatorando e acabando com o callback hell

Agora vamos refatorar a nossa aplicação com o intuito de usar um JavaScript mais moderno e pricipalmente
acabar com o callback hell.

Abra o arquivo src/database/Mongo.js.

Na função getDatabase, vamos retornar uma promise ao invés de usar callback deixando ela assim:

import { MongoClient as mongo } from 'mongodb';

const DATABASE = 'application';
const MONGODB_URI = 'mongodb://db:27017';

export const getDatabase = () => {
  const dbOptions = {
    useNewUrlParser: true,
  };
  const promiseCallback = (resolve, reject) => {
    mongo.connect(MONGODB_URI, dbOptions, (err, client) => {
      if (err) return reject(err);

      const db = client.db(DATABASE);
      resolve(db);
    });
  }
  return new Promise(promiseCallback);
};

Dentre as mudanças feitas, agora estamos utilizando import ao invés de require, const ao invés de var, estamos retornando uma promise ao invés de usar callback e também estamos utilizando o export ao invés do modules.exports.

Agora vamos atualizar o index.js para utilizar a getDatabase da forma correta.

import express from 'express';
import { getDatabase } from './src/database/Mongo';

const PORT =  3000;
const HOST = '0.0.0.0';

const app = express();

app.use('/', async (req, res) => {
  try {
    const db = await getDatabase();
    const collection = db.collection('accounts');
    const result = await collection.insertOne({ name: 'Emerson Broga' });
    const data = result.ops[0];
    res.json({message: 'Account Created', data });
  }catch(e){
    res.json({message: `Something's wrong`, error: e});
  }
});

app.listen(PORT, function() {
  console.log('Running on port ' + PORT);
});

Vamos entender o que mudou no index.js, primeiramente estamos usando import from no lugar do require, const no lugar de var, no callback da rota ‘/’ estamos usando arrow function e async/await.

Dentro dessa arrow function, temos um try/catch, e dentro dele, aguardamos a promise getDatabase retornar o nosso db.

Com o db em mãos, pegamos o collection account.

Com o collection, inserimos os dados no banco usando o insertOne e aguardamos o resultado de retorno.

Por fim pegamos do resultado os dados que precisamos respondemos a requisição com um json que contem os dados e uma mensagem.

Caso o try/catch lance algum erro, o nosso catch irá responder a requisição com um json que contem o erro e uma mensagem.

Ao atualizar o navegador, você agora irá ver o json de resposta com a mensagem "Account Created".

localhost:3000

localhost:3000

Listando os dados com MongoDB

Até agora nesse projeto, temos apenas uma rota que ao ser chamada cria um novo registro no banco.

Vamos mudar essa parte de forma que a rota inicial / tenha a listagem dos dados do banco e a rota /create seja a rota que cria um novo registro no banco.

No nosso index.js, vamos criar a rota create apenas mudando o seguinte:

De :

app.use('/', async (req, res) => { ... }

para:

app.use('/create', async (req, res) => { ... }

E agora vamos criar nossa rota principal que exibe a listagem de registros:

app.use('/', async (req, res) => {
  try {
    const db = await getDatabase();
    const collection = db.collection('accounts');
    const data = await collection.find({}).toArray();
    res.json({message: 'Accounts found', data });
  }catch(e){
    console.error(e);
    res.json({message: `Something's wrong`, error: e});
  }
});

Agora a rota principal, exibe a listagem e para isso, dentro do callback da nossa rota, pegamos o db, em seguida o collection e por fim usamos o método find do collection para buscar os registros que transformamos em Array e retornamos em um json com a mensagem "Accounts found".

O seu index.js deve estar assim ness ponto:

import express from 'express';
import { getDatabase } from './src/database/Mongo';

const PORT =  3000;
const HOST = '0.0.0.0';

const app = express();

app.use('/create', async (req, res) => {
  try {
    const db = await getDatabase();
    const collection = db.collection('accounts');
    const result = await collection.insertOne({ name: 'Emerson Broga' });
    const data = result.ops[0];
    res.json({message: 'Account Created', data });
  }catch(e){
    console.error(e);
    res.json({message: `Something's wrong`, error: e});
  }
});


app.use('/', async (req, res) => {
  try {
    const db = await getDatabase();
    const collection = db.collection('accounts');
    const data = await collection.find({}).toArray();
    res.json({message: 'Accounts found', data });
  }catch(e){
    console.error(e);
    res.json({message: `Something's wrong`, error: e});
  }
});

app.listen(PORT, function() {
  console.log('Running on port ' + PORT);
});

Ao atualizar o navegador, na rota principal, você terá a listagem de todos esses registro que criamos no banco enquanto estávamos criando a aplicação.

 localhost:3000

localhost:3000

E se você for em /create você irá inserir mais um registro no banco e terá a mensagem "Account Created" no json retornado.

Organizando rotas em um projeto com Express

Vamos continuar alterando o nosso projeto e organizando as coisas.

Como você já deve ter percebido, se a aplicação continuar crescendo desse jeito o arquivo index.js vai ficar gigante, pois cada nova rota, significa adicionar mais um tanto de coisa no index.

Então vamos criar agora um arquivo para gerenciar as rotas e vamos criar controllers que serão responsáveis pelo processamento de cada rota.

Dentro da pasta src, vamos criar um arquivo chamado routes.js.
Dentro da pasta src, vamos criar também uma pasta chamada controllers.
Dentro da pasta controllers, vamos criar um arquivo chamado AccountController.js.

No src/controllers/AccountController.js vamos colocar as funções de listar e criar registros:

import { getDatabase  } from '../../database/Mongo';

export default {
  list: async (req, res) => {
    try {
      const db = await getDatabase();
      const collection = db.collection('accounts');
      const data = await collection.find({}).toArray();
      res.json({message: 'Accounts found', data });
    }catch(e){
      console.error(e);
      res.json({message: `Something's wrong`, error: e});
    }
  },
  create: async (req, res) => {
    try {
      const db = await getDatabase();
      const collection = db.collection('accounts');
      const result = await collection.insertOne({ name: 'Emerson Broga' });
      const data = result.ops[0];
      res.json({message: 'Account Created', data });
    }catch(e){
      console.error(e);
      res.json({message: `Something's wrong`, error: e});
    }
  },
};

Nesse arquivo, basicamente importamos o getDatabase no começo e em seguida exportamos um objeto com as funções list e create.

Agora vamos pra o src/routes.js e criar as rotas que irão utilizar esse controller.

import express from 'express';
const router = express.Router();

import AccountController from './controllers/AccountController';

router.get('/', AccountController.list);
router.get('/create', AccountController.create);

export default router;

Nesse arquivo, importamos o express e do express pegamos o router.

Em seguida importamos o AccountController e depois criamos a rota para o / que chama o AccountController.list e também criamos a rota para o /create que chama o AccountController.create.

Agora temos apenas que “limpar” o nosso index.js.

import express from 'express';
import routes from './src/routes';

const PORT =  3000;
const HOST = '0.0.0.0';

const app = express();

app.use('/', routes);

app.listen(PORT, function() {
  console.log('Running on port ' + PORT);
});

Muito melhor dessa forma não é mesmo?

O nosso index.js está mais limpo e mais fácil de entender.

Vamos rodar no browser e ver se tudo continua funcionando como esperado.

Visite localhost:3000/ e localhost:3000/create.

Configurando o adminmongo no Docker

Uma outra coisa que foi falada no escopo desse projeto, foi o AdminMongo.

O admin mongo é um projeto que disponibliza uma interface web para que possamos facilmente operar o mongodb pelo browser.

Vamos adicionar um novo container ao docker que terá o monogoadmin.

Para isso vamos abrir o nosso docker-compose.yaml e adicionar o seguinte:

adminmongo:
  image: "mrvautin/adminmongo"
  ports:
    - "1234:1234"
  environment:
    - DB_HOST=db
    - DB_PORT=27017
    - HOST=0.0.0.0
  depends_on:
    - db

Estamos adicionando o adminmongo a partir de uma imagem chamada mrvautin/adminmongo, configuramos para responder na porta 123456, configuramos algumas variáveis de ambiente, como o DB_HOST, DB_PORT e HOST.

Agora basta rodar os seguintes comandos para atualizar nossos containers com o docker-compose.

docker-compose build
docker-compose up

Quando você for no browser em http://localhost:1234/, você será redirecionado para http://localhost:1234/app/connection_list e verá a tela inicial do adminMongo.

Admin Mongo

Admin Mongo

Vamos adicionar uma conexão:

Connection name: application
Connection string: mongodb://db:27017

E clique em “Add Connection”.

Se tudo ocorrer como esperado, você tera sua conexão e em “Action” você terá a opção “Connect”.
Clique em “Connect”.

AdminMongo Connections

AdminMongo Connections

Você terá algumas informações sobre o banco:

AdminMongo Database

AdminMongo Database

Na barra da esquerda, selecione a nossa collection “accounts” dentro do banco “application”.

Você verá a os dados dessa nossa collection, nesse ponto sugiro que você apague os registros, clicando em “Delete all” (que fica no lado direito, logo abaixo do total de registros).

AdminMongo Collections

AdminMongo Collections

Pronto agora temos um banco de dados limpo.

Adicionando o Faker ao projeto com NodeJs

Um dos requisitos do escopo dessa aplicação é criar registros aleatórios em nosso banco, mas no momento nossa rota /create cria apenas registros iguais, contendo o name “Emerson Broga”.
Para criar registros aleatórios, vamos utilizar uma biblioteca chamada Faker.

Vamos adicioná-la indo ao terminal e instalando via npm:

npm install --save faker

Agora atualize o container com:

docker-compose build
docker-compose up

Vamo abrir o nosso src/controllers/AccountController.js e importar a biblioteca faker.

import faker from 'faker';

Agora vamos usar o método faker.helpers.createCard(), ele retorna objeto com uma variedade de dados que iramos atribuir a uma variável account e iremos passar essa account para o nosso collection.insertOne;

Nesse ponto o seu src/controllers/AccountController.js deve estar assim:

import faker from 'faker';
import { getDatabase  } from '../database/Mongo';

export default {
  list: async (req, res) => {
    try {
      const db = await getDatabase();
      const collection = db.collection('accounts');
      const data = await collection.find({}).toArray();
      res.json({message: 'Accounts found', data });
    }catch(e){
      console.error(e);
      res.json({message: `Something's wrong`, error: e});
    }
  },
  create: async (req, res) => {
    try {

      const account = faker.helpers.createCard(); 
      const db = await getDatabase();
      const collection = db.collection('accounts');
      const result = await collection.insertOne(account);
      const data = result.ops[0];
      res.json({message: 'Account Created', data });
    }catch(e){
      console.error(e);
      res.json({message: `Something's wrong`, error: e});
    }
  },
};

Se tudo estiver correto, vá ao browser em `localhost:3000/create e você verá que a account que foi criada contem uma variedade bem grande de dados.

Algo como:

localhost:3000

localhost:3000

Se voltarmos a listagem em localhost:3000, você vai ver que estamos exibindo também todos os dados. Na listagem seria interessante que a gente listasse apenas alguns dados como `name`, `username` e `email`.

Para isso, vamos definir os campos a serem retornados em nossa listagem usando o project.

Em src/controllers/AccountController.js no método list, vamos criar um objeto com os campos que gostaríamos de ter na listagem da seguinte forma:

const fields = {
  name: true,
  username: true,
  email: true,
}  

E vamos passar esse objeto para o método project do collection da seguinte forma:

const data = await collection.find({}).project(fields).toArray();

Agora ao visitar localhost:3000 no browser veremos apenas o name, username e email.

localhost:3000

localhost:3000

Perceba que o _id também é retornado. Esse é o padrão do MongoDb, caso você não queira que o _id, seja retornado, você deve explicitamente passar _id: false, nos fields.

Configurando variáveis de ambiente e caminhos absolutos no nodejs com docker

Nesse ponto da aplicação, já temos o nosso escopo inicial concluído, porem quero fazer algumas pequenas melhorias.

A primeira delas é configurar algumas variáveis de ambiente e também os caminhos absolutos no docker.

As variáveis de ambiente podem ser lidas dentro do node usando o process.env.NOME_DA_VARIAVEL.

Vamos abrir o docker-compose.yaml e criar as variáveis de ambiente em services > web > environment:

 environment:
  - PORT=3000
  - MONGODB_DATABASE=application
  - MONGODB_URI=mongodb://db:27017
  - NODE_PATH=src/

Esse último valor NODE_PATH que irá nos possibilitar usar os caminhos absolutos ao fazer require dentro de nossa aplicação.

Com isso o docker-compose.yaml deve ficar assim:

version: "3"
services:
  web:
    build: .
    command: npm start
    volumes:
      - .:/usr/app
    ports:
      - "3000:3000"
    environment:
      - PORT=3000
      - MONGODB_DATABASE=application
      - MONGODB_URI=mongodb://db:27017
      - NODE_PATH=src/
    depends_on:
      - db
  adminmongo:
    image: "mrvautin/adminmongo"
    ports:
      - "1234:1234"
    environment:
      - DB_HOST=db
      - DB_PORT=27017
      - HOST=0.0.0.0
    depends_on:
      - db
  db:
    image: mongo
    volumes:
      - ./data:/data/db
    ports:
      - "27017:27017"

Agora atualize o container com:

docker-compose build
docker-compose up

Agora vamos refatorar nossos paths e também utilizar nossas variáveis de ambiente.

Em src/database/Mongo.js vamos mudar de:

const DATABASE = 'application';
const MONGODB_URI = 'mongodb://db:27017';

para:

const DATABASE = process.env.MONGODB_DATABASE;;
const MONGODB_URI = process.env.MONGODB_URI;

Vamos aproveitar que estamos no nosso Mongo.js e vamos criar adicionar o seguinte:

export const COLLECTION_ACCOUNTS = 'accounts';

export const getCollection = (collectionName) => {
  return getDatabase().then(db => db.collection(collectionName))
}

Nesse ponto, o arquivo Mongo.js deve estar assim:

const mongo = require('mongodb').MongoClient

const DATABASE = process.env.MONGODB_DATABASE;
const MONGODB_URI = process.env.MONGODB_URI;

export const COLLECTION_ACCOUNTS = 'accounts';

export const getDatabase = () => {
  const dbOptions = {
    useNewUrlParser: true,
  };
  const promiseCallback = (resolve, reject) => {
    mongo.connect(MONGODB_URI, dbOptions, (err, client) => {
      if (err) return reject(err);

      const db = client.db(DATABASE);
      resolve(db);
    });
  }
  return new Promise(promiseCallback);
};

export const getCollection = (collectionName) => {
  return getDatabase().then(db => db.collection(collectionName))
}

Agora vamos para o src/controllers/AccountController.js;

Vamos começar utilizando o caminho absoluto para importar as funções do Mongo e vamos chamar a getCollection no lugar de getDatabase e também importar a COLLECTION_ACCOUNTS.

Mudamos disso:

import { getDatabase  } from '../database/Mongo';

Para isso:

import { getCollection, COLLECTION_ACCOUNTS  } from 'database/Mongo';

Agora vamos usar a getCollection nos métodos list e create.

O AccountController vai ficar assim:

import faker from 'faker';
import { getCollection, COLLECTION_ACCOUNTS  } from 'database/Mongo';

export default {
  list: async (req, res) => {
    try {
      const fields = {
        name: true,
        username: true,
        email: true,
      };
      const collection = await getCollection(COLLECTION_ACCOUNTS);
      const data = await collection.find({}).project(fields).toArray();
      res.json({message: 'Accounts found', data });
    }catch(e) {
      console.error(e);
      res.json({message: `Something's wrong`, error: e});
    }
  },
  create: async (req, res) => {
    try {
      const account = faker.helpers.createCard(); 
      const collection = await getCollection(COLLECTION_ACCOUNTS);
      const result = await collection.insertOne(account);
      const data = result.ops[0];
      res.json({message: 'Account Created', data });
    }catch(e) {
      console.error(e);
      res.json({message: `Something's wrong`, error: e});
    }
  },
};

No arquivo src/routes.js vamos importar o AccountController usando o caminho absoluto.

Mudando de:

import AccountController from './controllers/AccountController';

para:

import AccountController from 'controllers/AccountController';

E por fim vamos remover o caminho relativo do index.js e colocar o caminho absoluto.

Mudando de:

import routes from './src/routes';

para:

import routes from 'routes';

Conclusão

Acredito que esse post possa ajudar muita gente e de diversos níveis pois vimos várias coisas diferentes aqui.
Apesar do escopo do projeto ser bem pequeno tem muita coisa envolvida e geralmente é difícil achar um lugar só que explique tudo.

Se você leu o post todo e acompanhou fazendo o passo-a-passo em sua máquina, eu tenho que lhe dar os parabéns, isso significa que você realmente quer aprender.

Se você teve alguma dificuldade ou dúvida, por favor deixe um comentário, e na medida do possível eu vou tentar esclarecer todas as dúvidas que venham a surgir.

Código completo

Você encontra o código completo no https://github.com/emersonbrogadev/docker-node-mongo-random-users.

Obriagado pela leitura, compartilhe!

Se você gostou desse post, compartilhe com seus amigos, ajude a espalhar conhecimento!
Não se esqueça de nos seguir nas redes sociais, por que você não pode ficar de fora!

  1. Faça parte da nossa lista de Desenvolvedores 📨
  2. Se inscreva em nosso canal do Youtube 📺
  3. Curta nossa página no Facebook 👍
  4. Não perca as atualizações no Twitter 🐦
  5. Veja as dicas no Instagram 📸
  6. Siga nossos repositórios no Github ⌨️

Foto por Owen Spencer no Unsplash


@emersonbroga

Programador há mais de 15 anos, sou formado em Desenvolvimento de Sistemas pela Faculdade Pitágoras e pós-graduado em Gestão Estratégica de Marketing pela PUC Minas. Trabalhei em diversas agências de publicidade e desenvolvimento de software e atualmente trabalho em projetos internacionais como FOX.com, FXNetworks.com, NatGeo.com entre outros. Estou atualmente dedicando meu tempo a ensinar programação em meu blog e redes sociais. Saiba mais em https://emersonbroga.com/e/sobre/.