Vous êtes sur la page 1sur 173

Be

MEAN - Node.js

Tabela de contedos
Introduo

Teoria

V8

1.1

Single thread

1.2

Event Loop

1.3

I/O Async

1.4

Libuv

1.5

Event Driven

1.6

API

1.7

Instalao

HTTP

createServer

3.1

Methods

3.2

Status Codes

3.3

Rotas

3.4

get

3.5

request

3.6

post

3.7

put

3.8

Callbacks

FileSystem

mkdir

5.1

open

5.2

readdir

5.3

readFile

5.4

rename

5.5

writeFile

5.6

Be MEAN - Node.js
npm

6
init

6.1

install --global ou -g

6.2

install --save

6.3

install --save-dev

6.4

install --optional

6.5

run

6.6

Globals

CommonJS

7.1

Process

Mongoose

Create

9.1

Retrieve

9.2

Update

9.3

Delete

9.4

Eventos
Mongoose
Promises
Mongoose
Mongoose

10
10.1
11
11.1
12

Arquitetura Atmica

12.1

Validate

12.2

Populate

12.3

Plugins

12.4

TDD

13

assert

13.1

chai

13.2

Teste Atmicos

14

Express

15

Model

15.1

Be MEAN - Node.js
Controller

15.2

Routes

15.3

Express

16

Events

16.1

Promises

16.2

Middlewares

16.3

Express

17

TDD

17.1

Socket.io

18

TDD

18.1

REPL

19

UDP

20

TCP

21

HTTPS

22

Utilizando APIs externas

23

pokemons

23.1

github

23.2

redtube

23.3

instagram

23.4

Autenticao

24

Session

24.1

Express

24.2

Mongoose/MongoDB

24.3

Autenticao
Token

25
25.1

Be MEAN - Node.js

Node.js
Nesse curso abordarei a verso 5 do Node.js.
O MongoDb um banco e dados NoSQL open-source e orientado a documentos
JSON. Ele foi criado para ser escalado horizontalmente, conceito que veremos
mais a frente.

ndice
Teoria
V8
Single thread
Event Loop
I/O Async
Libuv
Event Driven
API
Instalao
HTTP
createServer
Methods
Status Codes
Rotas
get
request
post
put
Callbacks
FileSystem
mkdir
open
readdir
readFile

Introduo

Be MEAN - Node.js
rename
writeFile
npm
init
install --global ou -g
install --save
install --save-dev
install --optional
run
Globals
CommonJS
Process
Mongoose
Create
Retrieve
Update
Delete
Eventos
Mongoose
Promises
Mongoose
Mongoose
Arquitetura Atmica
Validate
Populate
Plugins
TDD
assert
chai
Teste Atmicos
Express
Model
Controller
Routes
Express
Events

Introduo

Be MEAN - Node.js
Promises
Middlewares
Express
TDD
Socket.io
TDD
REPL
UDP
TCP
HTTPS
Utilizando APIs externas
pokemons
github
redtube
instagram
Autenticao
Session
Express
Mongoose/MongoDB
Autenticao
Token

Code Style
Code Style
Types
References
Objects
Arrays
Destructuring
Strings
Functions
Arrow Functions
Constructors
Modules

Introduo

Be MEAN - Node.js
Iterators and Generators
Properties
Variables
Hoisting
Comparison Operators & Equality
Blocks
Comments
Whitespace
Commas
Semicolons
Type Casting & Coercion
Naming Conventions
Accessors
Events
jQuery
ECMAScript 5 Compatibility
ECMAScript 6 Styles
Testing
Performance
Resources
In the Wild
Translation
The JavaScript Style Guide Guide
Chat With Us About JavaScript
Contributors
License

Introduo

Be MEAN - Node.js

Teoria

Node.js um interpretador de JavaScript que funciona do lado do servidor criado


em cima do V8 que o motor de JavaScript da Google e que roda no seu
Chrome, alm disso ele conta com outras bibliotecas que o auxiliam no
gerenciamento dos processor, como por exemplo a Libuv que falaremos mais
adiante.
O Node.js age como uma ponte entre uma API acessvel via JavaScript e
funes em C++ do V8, foi criado por Ryan Dahl em 2009.
Conta-se que Ryan se inspirou depois de ver barra de progresso de upload de
arquivos no Flickr, percebeu que o navegador no sabia o quanto do arquivo foi
carregado e tinha que consultar o servidor web. Loco no?
O Node.js pode ser considerado uma plataforma de execuo de aplicaes em
JavaScript no lado do servidor, como visto na imagem abaixo.

Teoria

Be MEAN - Node.js

V8

Teoria

10

Be MEAN - Node.js

Ento o que o tal do V8 que a base fundamental do Node.js?


Ele nada menos que o interpretador de JavaScript, tipo uma mquina virtual,
desenvolvido pelo Google e usado no Chrome. Feito em C++ e open-source.
O trabalho dele basicamente compilar o cdigo de JavaScript para o cdigo
nativo de mquina para depois execut-lo. Ele levou a velocidade dos cdigos
compilados para o JavaScript.

Single thread

Teoria

11

Be MEAN - Node.js

O Node.js trabalha apenas com uma thread, podendo ser criadas outras, com
isso economizando muita memria, diferentemente da forma que o Apache
trabalha e voc percebe claramente a diferena de utilizao de memria, j que
com apenas uma thread voc no precisa criar um processo novo para cada
usurio conectado, acarretando tambm em uma economia de CPU.
Mas como ele consegue gerenciar a porra toda apenas com uma thread?
Com uma coisinha linda fachamada Event Loop.

Event Loop

Teoria

12

Be MEAN - Node.js

O Event Loop nada mais que uma fila infinita que recebe todos os eventos
emitidos pelo Node.js, isso inclui as requisies que recebemos no servidor
HTTP.

Quando o evento chega para ser exeutado no Event Loop, caso ele seja
assncrono, ele ser enviado para onde deve ser executado, por exemplo:
filesystem, network, process, etc.
Como o processo assncrono ele ir executar e s aps sua finalizao que
ele dispara o trigger para seu callback, esse voltando para a fila que ir ser
executada pelo Event Loop.

Teoria

13

Be MEAN - Node.js

Libuv

Libuv uma biblioteca multi-plataforma que fornece notificao de eventos de


forma assncrona, isso inclui nosso sagrado I/O, foi originalmente desenvolvida
para o Node.js, sendo a maior motivao a integrao com o Windows.
Essa biblioteca veio para fazer o trabalho da libev e libeio agregando tambm a
parte de DNS do C-Ares.
Onde a libev gerenciava o Event Loop e a libeio gerenciava o I/O
assncrono.

Foi no Node 0.5 que ela entrou em cena e na verso 0.9 a libev foi removida.

Features
Full-featured event loop backed by epoll, kqueue, IOCP, event ports.
Asynchronous TCP and UDP sockets
Asynchronous DNS resolution
Asynchronous file and file system operations
File system events
ANSI escape code controlled TTY
IPC with socket sharing, using Unix domain sockets or named pipes
(Windows)

Teoria

14

Be MEAN - Node.js
Child processes
Thread pool
Signal handling
High resolution clock
Threading and synchronization primitives
Lista retirada da documentao
Caso voc queira se aprofundar mais indico esse material.

I/O Async
Qualquer funo do Node.js, por padro, assncrona por isso sempre
precisamos de uma funo que executar aps o final desse processamento,
essa que executa posteriormente chamada de callback, falaremos muito mais
sobre isso futuramente.
Mas ento o que quer dizer que o I/O assncrono?
Basicamente diz que qualquer leitura ou escrita de dados no espera seu
processo finalizar para continuar o script, nesse caso os processos ocorrem
paralelamente execuo.
Para termos uma ideia melhor de como o funcionamento assncrono, vamos
pensar um restaurante sendo sncrono.

Teoria

15

Be MEAN - Node.js

No restaurante sncrono quando uma mesa atendida ela precisa receber seu
pedido antes que o garom possa antender outra mesa!!!

Agora no restaurante assncrono o mesmo garom pode atender vrios pedidos


e envi-los para a cozinha. Ser a cozinha a responsvel por responder cada
pedido na ordem que para eles forem mais importantes ou mais rpidos. Nesse
caso a ordem da resposta dos pedidos pode ser diferente da ordem pedida para
a cozinha.

Teoria

16

Be MEAN - Node.js
Quando um pedido finalizado no Restaurante Assncrono uma
campainha/evento emitido.

Agora no Restaurante Assncrono o garom pode atender todas as mesas que


existirem apenas enviando seus pedidos para serem executados na cozinha.

O mesmo acontece com nossos sistemas, quando voc envia uma requisio
assncrona voc no tem a certeza quando ela ir retornar, por isso usamos
Promises, mas isso um assunto posterior.
Isso me lembrou o Princpio da incerteza de Heisenberg na fsica, mais algum
pira nisso como eu? :p

Teoria

17

Be MEAN - Node.js

Thread Pool

O node.js trabalha uma tread-pool a partir de uma thread, a thread em que ele
instanciado, facilmente confundvel com o que diz respeito a multi-thread,
porm o node no dispe desse custoso mecanismo para fazer i/o assncrono, se
voc quiser dispor de todos os mecanismos de ncleos de um processador, voc
pode usar mecanismo de balanceamento e comunicao entre portas,
cluster(um mdulo nativo do node.js).
Definio: Uma thread-pool pode ser comparada com um array em que o
nmero de colunas representaria uma idle-thread, uma thread pr executada
que s espera um processo para trabalhar e o processo, o indice desse array. O
node.js trabalha o processo como um I/O que a aplicao em node.js faz. Ou seja
um I/O para uma idle-thread.

Event Driven
API
A api do node.js consiste de uma forte influncia de outras plataformas.
O unix, sistema operacional usado como base para osx e linux, uma forte
influncia para o node.js, no atoa que roda muito bem no unix, discartando o
mrito do nosso amiguinho ruindows* do tio Gates.
Por causa dessa influncia o node.js extensivamente modular, possuindo
mdulos para tudo, inclusive a sua aplicao ser tratada como mdulo para o
node.js. Ento muito importante ter um cdigo consistente, pouco dependente,
pouco aclopado, e por fim, modularizado :)

Teoria

18

Be MEAN - Node.js

Instalao
Voc pode baixar os instaladores em nodejs.org/en/download/
Ou utilizando o brew, para Mac, ou apt-get/yum para Linux.

apt-get
curl -sL https://deb.nodesource.com/setup_5.x | sudo -E bash sudo apt-get update
sudo apt-get install nodejs

Teste a verso do Node.js como seguinte comando:


node -v
v5.0.0

Caso voc receba um erro que o comando node no exista, faa o seguinte
link:
sudo ln -s /usr/bin/nodejs /usr/bin/node

E instale tambm o build-essential .


apt-get install --yes build-essential

Pois no Linux h um pequeno problema pois o pacote node j existia, ento


para os Linuxes voc tambm pode instalar o node-legacy , caso voc no o
instale ou no crie o link acima, precisar chamar o no Node com nodejs e no
node como o padro, ento esse pacote/link faz essa mgica acontecer.

Instalao

19

Be MEAN - Node.js

Teste para ver se o npm tambm foi instalado:


npm -v

Caso o terminal mostre um erro ento instale-o:


sudo apt-get install npm

Caso tenha vindo uma verso antiga, a atual a 5, voc pode fazer adicionar
um PPA (arquivo de pacotes pessoais) mantido pelo NodeSource.
Primeiro, voc precisa instalar o PPA de modo a obter acesso ao seu contedo:
curl -sL https://deb.nodesource.com/setup | sudo bash -

yum
sudo yum install nodejs npm --enablerepo=epel

A opo --enablerepo=epel faz o yum procurar por pacotes no repositrio


EPEL.

Instalao

20

Be MEAN - Node.js
EPEL (Extra Packages for Enterprise Linux) is open source and free
community based repository project from Fedora team which provides 100%
high quality add-on software packages for Linux distribution including RHEL
(Red Hat Enterprise Linux), CentOS, and Scientific Linux. Epel project is not a
part of RHEL/Cent OS but it is designed for major Linux distributions by
providing lots of open source packages like networking, sys admin,
programming, monitoring and so on. Most of the epel packages are
maintained by Fedora repo.
Via http://www.tecmint.com/how-to-enable-epel-repository-for-rhel-centos-6-5/

brew
Tendo o brew instalado basta executar:
brew update
brew install node

E pronto.
Mas tudo isso apenas se voc no utilizar o instalador disponilizado aqui.

Para outros sistemas voc pode ver como instalar aqui

Instalao

21

Be MEAN - Node.js

HTTP
O mdulo http o principal mdulo da nossas aplicaes pois com ele que
criamos um servidor web para fornecer nossos sistemas.
Ele trabalha com diversas funcionalidades do protocolo HTTP, porm no iremos
abranger todas.
Esse um mdulo nativo, no necessitando que seja instalado anteriormente.

Como usar
Para utilizar esse mdulo basta import-lo para seu cdigo:
require('http')

Cada requisio enviada possui cabealhos que dizem o que essa requisio faz,
vamos ver um exemplo de uma requisio GET :
GET / HTTP/1.1
Host: webschool.io
Connection: close
User-Agent: Chrome/46.0.2490.86
Accept-Encoding: gzip
Accept-Charset: ISO-8859-1,UTF-8;q=0.7,*;q=0.7
Cache-Control: no
Accept-Language: de,en;q=0.7,en-us;q=0.3
Referer:

Veja a primeira linha: GET / HTTP/1.1 . Onde o GET o verbo do HTTP da


nossa requisio , falarei mais adiante sobre.
Aps enviada essa requisio pelo cliente o servidor ir tratar e retorna uma
resposta, seguindo o exemplo abaixo:

HTTP

22

Be MEAN - Node.js

HTTP/1.1 200 OK
Connection: keep-alive
Content-Encoding: gzip
Content-Type: text/html
Date: Sun, 06 Dec 2015 01:07:17 GMT
ETag: W/'55f9df1c-23f'
Last-Modified: Wed, 16 Sep 2015 21:29:00 GMT
Server: nginx
Transfer-Encoding: chunked
Vary: Accept-Encoding

Note na primeira linha onde recebemos: HTTP/1.1 200 OK .


Esse 200 o cdigo de status da nossa resposta, falarei mais adiante sobre.
Ento perceba que o cliente envia uma requisio com um verbo HTTP e seus
cabealhos, depois de recebida e tratada o servidor responde com um STATUS
CODE e seus cabealhos.

Methods
O protocolo HTTP possui um conjunto de mtodos/verbos que o cliente pode
invocar, veja abaixo a lista dos verbos mais usados:
GET: Requisita um representao do recurso especificado (O mesmo
recurso pode ter vrias representaes, ao exemplo de servios que
retornam XML e JSON). HEAD: Retorna os cabealhos de uma resposta
(sem o corpo contendo o recurso)
POST: Envia uma entidade e requisita que o servidor aceita-a como
subordinada do recurso identificado pela URI.
PUT: Requisita que um entidade seja armazenada embaixo da URI
fornecida. Se a URI se refere a um recurso que j existe, ele modificado; se
a URI no aponta para um recurso existente, ento o servidor pode criar o
recurso com essa URI.
DELETE: Apaga o recurso especificado.
TRACE: Ecoa de volta a requisio recebida para que o cliente veja se
houveram mudanas e adies feitas por servidores intermedirios.

HTTP

23

Be MEAN - Node.js
OPTIONS: Retorna os mtodos HTTP que o servidor suporta para a URL
especificada.
CONNECT: Converte a requisio de conexo para um tnel TCP/IP
transparente, usualmente para facilitar comunicao criptografada com SSL
(HTTPS) atravs de um proxy HTTP no criptografado.
PATCH: Usado para aplicar modificaes parciais a um recurso.
E so com 4 verbos diferentes que criamos um CRUD, que essencial em
qualquer sistema.
No CRUD precisamos ter 4 aes:
Create
Retrieve/Read
Update
Delete
Sabendo disso agora, quais so os 4 verbos que utilizamos para o CRUD?
ps: Faz parte do exerccio dessa aula.

Status Codes
Os cdigos de retorno HTTP so compostos por 3 dgitos que seguem um
formato padro dando melhor direcionamento para a identificao correta do
retorno.
Os cdigos de status so divididos em:

1XX Informacional
No h necessidade de se preocupar com este, serve apenas para informar que
a informao foi recebida e que o processo continua.

2XX Sucesso
Significa que o pedido foi recebido com sucesso. o que sempre acontece
quando suas pginas so carregadas

HTTP

24

Be MEAN - Node.js
200 OK. O pedido ao servidor foi atendido com sucesso. A pgina web existe e
ser enviada ao user-agent (navegador, rob de busca).

3XX Redirecionamento
Serve para avisar direto no cabealho HTTP uma mudana de pgina. Diferente
de um Meta Refresh ou usar javascript, ele permite um redirecionamento suave
e importante para SEO.
301 Movido Permanentemente. Muito til para redirecionar pginas. Serve para
redirecionar suas URLs que foram movidas permanentemente. Assim voc evita
pginas de cdigo 404 ou pode tornar URLs dinmicas com em URLs limpas.
302 Movido Temporariamente. Serve tambm para mover, mas com funo
temporria. A vantagem que voc pode reverter isto. Funciona bem para
manutenes ou alterao no definitiva. O rob de busca continua visitando o
endereo original.

4XX Erro do Cliente


Deve ser tratado com ateno pois o contedo no estar acessvel para o
visitante nem para o site de busca. Problema para indexar.
401 No autorizado. O acesso a pgina no esta autorizado pois possivelmente
a pessoa no est logada. Isto impede de uma pgina ser indexada por exemplo.
403 Proibido. Neste caso o rob de busca tambm no ter como indexar o
contedo.
404 No encontrado. o cdigo de retorno pode ser uma pgina ou arquivo
que no existe no servidor, como um arquivo apagado. Pode ser usado para
apresentar uma pgina com contedos relacionados URL procurada.

5XX Erro do Servidor


O servidor no consegui atender o pedido por algum erro. Tambm no permitir
a indexao da pgina.
500 Erro interno do servidor.
HTTP

25

Be MEAN - Node.js
503 Servio indisponvel. Pode ser um erro temporrio. Uma manuteno ou
uma grande quantidade de acessos pode derrubar o servidor.
Lista dos cdigos de status.

Cats API

Tem at uma API para consultar o STATUS CODE felino.

createServer
Antes de iniciar a criao dos cdigos desse mdulo crie uma pasta para o
Workshop e dentro dela uma para o Node.js, por exemplo:
workshop-be-mean/nodejs/

HTTP

26

Be MEAN - Node.js
Para iniciarmos um servidor HTTP utilizaremos a funo createServer que
recebe uma funo com 2 parmetros:
request;
response.
// hello-world.js
var http = require('http');
http.createServer(function(request, response){
response.writeHead(200, {'Content-Type': 'text/plain'});
response.write('Be MEAN');
response.end();
}).listen(3000, function(){
console.log('Servidor rodando em localhost:3000');
});

Ou:
var http = require('http');
var server = http.createServer(function(request, response){
response.writeHead(200, {'Content-Type': 'text/plain'});
response.write('Be MEAN');
response.end();
});
server.listen(3000, function(){
console.log('Executando Servidor HTTP');
});

A nica diferena que no primeiro cdigo no atribuimos o servidor em uma


varivel por isso encadeamos a funo listen que faz o servidor subir na
porta passada para ela e executando um callback aps.
Percebeu que uma funo sem nome foi passada para o createServer ?

HTTP

27

Be MEAN - Node.js

function(request, response){
response.writeHead(200, {'Content-Type': 'text/plain'});
response.write('Be MEAN');
response.end();
}

Isso se chama funo annima e uma caracterstica muito importante do


JavaScript, nessa funo respondemos para o cliente que fez a requisio.
Exemplo de um cabealho:
{ 'content-length': '123',
'content-type': 'text/plain',
'connection': 'keep-alive',
'host': 'mysite.com',
'accept': '*/*' }

Lista dos campos do cabealho, voltando ao nosso cdigo.


response.writeHead(200, {'Content-Type': 'text/plain'});

Acima estamos criando o cabealho da resposta com o Status Code 200 e o


Content-Type text/plain .
response.write('Be MEAN');

Depois escrevemos no corpo da resposta Be MEAN .


response.end();

E por fim finalizamos a conexo com o cliente.


Mas como estamos trabalhando com o Navegador para acessar nosso servidor
vamor retornar um HTML ento.

HTTP

28

Be MEAN - Node.js

// hello-http.js
var http = require('http');
http.createServer(function(request, response){
response.writeHead(200, {'Content-Type': 'text/plain'});
response.write('<h1>Be MEAN</h1>');
response.end();
}).listen(3000, function(){
console.log('Servidor rodando em localhost:3000');
});

U mas deu merda!


Ento como ns retornamos nossa resposta corretamente?
Corrigindo o cabealho da resposta.
// hello-http.js
var http = require('http');
http.createServer(function(request, response){
response.writeHead(200, {'Content-Type': 'text/html'});
response.write('<h1>Be MEAN</h1>');
response.end();
}).listen(3000, function(){
console.log('Servidor rodando em localhost:3000');
});

Aprendemos a enviar um HTML escrevendo ele na resposta, agora a hora de


respondermos com um HTML j criado, ento primeiramente crie um
index.html , na mesma pasta dos seus cdigos, com o seguinte contedo:

HTTP

29

Be MEAN - Node.js

<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset='UTF-8'>
<title>Be MEAN - Instagram</title>
</head>
<body>
<h1>Be MEAN - Instagram - html</h1>
</body>
</html>

Depois crie o seguinte script hello-html.js :


// hello-html.js
var http = require('http')
, fs = require('fs')
, index = fs.readFileSync('index.html');
http.createServer(function(request, response){
response.writeHead(200, {'Content-Type': 'text/html'});
response.end(index);
}).listen(3000, function(){
console.log('Servidor rodando em localhost:3000');
});

Nesse cdigo estamos lendo o index.html com o fs.readFileSync ,


falaremos mais tarde sobre o mdulo fs .
Com isso aprendemos como a criar um simples servidor HTTP para nossas
futuras aplicaes.

Rotas
A primeira coisa que nosso sistema web precisa ter para ser acessado so rotas,
ento vamos criar nossa primeira rota /api/v1 que retornar informaes
sobre a nossa api.

HTTP

30

Be MEAN - Node.js
Primeiramente vamos criar o JSON de resposta:
const JSON = {
version: 1.0
, name: 'Be MEAN'
, created_at: Date.now()
};

Depois vamos adicion-lo no script server.js que ir conter o nosso servidor


HTTP.
// server.js
'use strict';
const http = require('http')
, JSON = {
version: 1.0
, name: 'Be MEAN'
, created_at: Date.now()
};
http.createServer(function(request, response){
response.writeHead(200, {'Content-Type': 'application/json'});
response.end();
}).listen(3000, function(){
console.log('Servidor rodando em localhost:3000');
});

Sim iremos usar comma-first.


Esse ser nosso esqueleto, agora precisamos verificar qual a URL requisitada
pelo cliente.

HTTP

31

Be MEAN - Node.js

// server.js
'use strict';
var date = (new Date()).toJSON();
const http = require('http')
, SUCCESS = {
version: '1.0'
, name: 'Be MEAN'
, returned_at: date
}
, ERROR = {
message: 'DEU MERDA FI!!!!'
};
http.createServer(function(req, res){
if(req.url === '/api/v1') {
res.writeHead(200, {'Content-Type': 'application/json'});
res.write(JSON.stringify(SUCCESS));
}
else {
res.writeHead(400, {'Content-Type': 'application/json'});
res.write(JSON.stringify(ERROR));
}
res.end();
}).listen(3000, function(){
console.log('Servidor rodando em localhost:3000');
});

DICA: como iremos trabalhar em arquivos nicos, para eliminarmos o trabalho


manual de derrubarmos o servidor e levantarmos novamente, vamos instalar o
nodemon :

npm i -g nodemon

Instalamos ele globalmente com -g para que seja acessvel em linha de


comando.

HTTP

32

Be MEAN - Node.js
Tambm instale o POSTMAN que uma extenso para o Chrome, para
testarmos nossas APIs, ser de grande utilizao durante o curso.
Depois de ter feito tudo isso levante seu servidor com o nodemon :
nodemon server.js
6 Dec 16:05:26 - [nodemon] v1.3.8
6 Dec 16:05:26 - [nodemon] to restart at any time, enter `rs`
6 Dec 16:05:26 - [nodemon] watching: *.*
6 Dec 16:05:26 - [nodemon] starting `node server.js`
Servidor rodando em localhost:3000

Agora abra o POSTMAN e d um GET na url


http://localhost:3000/api/v1 .

Pronto conseguimos a resposta de sucesso, agora vamos requisitar uma URL


inexistente para vermos o retorno de erro.

HTTP

33

Be MEAN - Node.js

Querystring

HTTP

34

Be MEAN - Node.js

'use strict';
// file: hello-querystring.js
let http = require('http')
, url = require('url');
http.createServer(function(request, response){
var result = url.parse(request.url, true);
response.writeHead(200, {'Content-Type': 'text/html'});
response.write('<html><body>');
response.write('<h1>Query string</h1>');
response.write('<ul>');
for(var key in result.query){
response.write('<li>'+key+' : '+result.query[key]+'</li>');
}
response.write('</ul>');
response.write('</body></html>');
response.end();
}).listen(3000, function(){
console.log('Servidor rodando em localhost:3000');
});

get
Para dar continuidade no HTTP vamos ver um dos verbos mais usados, o GET .
Com ele iremos requisitar informaes na nossa ou em outras APIs e isso que
faremos agora, consultaremos a API dos Pokemons.
Usaremos a funo http.get seguindo o seguinte modelo:

HTTP

35

Be MEAN - Node.js

http.get({
hostname: 'localhost',
port: 80,
path: '/',
agent: false // criar um novo agente apenas para este pedido
}, function (res) {
// Faa algo com res
})

Agora criando a requisio para o nosso servidor que est rodando o helloquerystring.js :

// file: http-get-localhost-querystring.js
'use strict';
const http = require('http');
http.get({
hostname: 'localhost',
path: '/user?name=Suissa&teacher=true&age=31',
port: 3000,
agent: false
}, function (response) {
let body = '';
console.log('STATUS: ' + response.statusCode);
console.log('HEADERS: ' + response.headers);
response.on('data', function(data) {
body += data;
});
response.on('end', function() {
console.log('Resposta: ', body);
});
});

HTTP

36

Be MEAN - Node.js
Mas vamos fazer uma pequena modificao para vocs j se acostumarem com
[Arrow Functions] do [ES6]:
// file: http-get-localhost-querystring.js
'use strict';
const http = require('http');
http.get({
hostname: 'localhost',
path: '/user?name=Suissa&teacher=true&age=31',
port: 3000,
agent: false
}, (response) => {
let body = '';
console.log('STATUS: ' + response.statusCode);
console.log('HEADERS: ' + response.headers);
response.on('data', function(data) {
body += data;
});
response.on('end', function() {
console.log('Resposta: ', body);
});
});

Eu poderia ter omitido os () de (response) => , porm deixei para ficar mais
fcil a sua migrao.
Salve esse cdigo como http-get-localhost-querystring.js e execute
como visto abaixo:
node http-get-localhost-querystring.js
STATUS: 200
HEADERS: {"content-type":"text/html","date":"Sat, 12 Dec 2015 14:46:37 GMT"
Resposta: <html><body><h1>Be - MEAN</h1><h2>Query string</h2><ul><

HTTP

37

Be MEAN - Node.js
Agora vou explicar o que aconteceu no cdigo, primeiramente passamos o JSON
de configurao da requisio:
{
hostname: 'localhost',
path: '/user?name=Suissa&teacher=true&age=31',
port: 3000,
agent: false
}

E no segundo parmetro passamos a funo annima que executada aps a


requisio ser respondida:
(response) => {
let body = '';
console.log('STATUS: ' + response.statusCode);
console.log('HEADERS: ' + response.headers);
response.on('data', function(data) {
body += data;
});
response.on('end', function() {
console.log('Resposta: ', body);
});
}

Inicialmente criamos a varivel body que ir receber a resposta em si, porm


de uma forma diferente que estamos acostumados, pois precisamos concatenar
body += data os dados que chegam no evento data do response que

recebido pelo callback do get() .


A nica diferena entre o http.get() e http.request que o get() seta
o valor do verbo para GET e chama o req.end() automaticamente.
Percebeu que estamos usando 2 eventos do response ?
Isso acontece porque ele uma instncia do http.IncomingMessage.
HTTP

38

Be MEAN - Node.js
Um objeto IncomingMessage criado por http.Server ou
http.ClientRequest e passado como o primeiro argumento para o
request e response , respectivamente. Ele pode ser usado para acessar

resposta de status, os cabealhos e os dados em si.


O IncomingMessage implementa a interface de Readable Stream que nos d
alguns eventos importantes, como:
close: evento emitido quando qualquer tipo de stream foi fechada;
data: evento que recebe os dados da Stream;
end: evento emitido quando no h mais dados para ler;
error: evento emitido quando acontecer algum erro.
Sabendo de tudo isso podemos seguir para o request e comear a consumir
APIs externas.

request
Primeira coisa que precisamos fazer criar o JSON de configurao da
requisio:
const options = {
host: 'api.redtube.com'
, path: '/?data=redtube.Videos.searchVideos&search=Sasha%20Gray'
};

Depois criamos a funo de callback:

HTTP

39

Be MEAN - Node.js

function callback(res) {
console.log('STATUS: ' + res.statusCode);
console.log('HEADERS: ' + JSON.stringify(res.headers));
let data = '';
res.setEncoding('utf8');
res.on('data', (chunk) => {
data += chunk;
});
res.on('end', () => {
console.log('Dados finalizados: ', data)
})
}

Para depois passarmos os 2 para o request :


const req = http.request(options, callback);

Bastando assim apenas escutar o evento error e fechar a conexo com


req.end() :

req.on('error', (e) => {


console.log('ERROOOO: ' + e.message);
});
req.end();

Juntando todas essas partes criamos o arquivo http-request.js :

HTTP

40

Be MEAN - Node.js

// file: http-request.js
'use strict';
const http = require('http');
const options = {
host: 'api.redtube.com'
, path: '/?data=redtube.Videos.searchVideos&search=Sasha%20Gray'
};
function callback(res) {
console.log('STATUS: ' + res.statusCode);
console.log('HEADERS: ' + JSON.stringify(res.headers));
let data = '';
res.setEncoding('utf8');
res.on('data', (chunk) => {
data += chunk;
});
res.on('end', () => {
console.log('Dados finalizados: ', data)
})
}
const req = http.request(options, callback);
req.on('error', (e) => {
console.log('ERROOOO: ' + e.message);
});
req.end();

Depois basta executar esse script no terminal:

HTTP

41

Be MEAN - Node.js

node http-request.js
STATUS: 200

HEADERS: {'server':'nginx','date':'Sun, 06 Dec 2015 16:15:05 GMT','content-t


Dados finalizados: {'videos':[...]}

Vamos continuar com o request porm dessa vez consultaremos

Create - POST
Para executarmos a ao de Create usaremos o verbo POST , normalmente, na
mesma url que usamos o GET , requisitaremos em uma API externa pois ainda
no estamos trabalhando com banco aqui e queremos um efeito real.
Essa API est em http://webschool-io.herokuapp.com/ .
// file: http-request-post.js
'use strict';
const http = require('http');
const querystring = require('querystring');
const postData = querystring.stringify({
name: 'Jean Nascimento'
, type: 'professor'
});
const options = {
host: 'webschool-io.herokuapp.com'
, path: '/api/pokemons'
, method: 'POST'
, headers: {
'Content-Type': 'application/x-www-form-urlencoded'
, 'Content-Length': postData.length
}
};
function callback(res) {
console.log('STATUS: ' + res.statusCode);
console.log('HEADERS: ' + JSON.stringify(res.headers));

HTTP

42

Be MEAN - Node.js
let data = '';
res.setEncoding('utf8');
res.on('data', (chunk) => {
data += chunk;
});
res.on('end', () => {
console.log('Dados finalizados: ', data)
})
}
const req = http.request(options, callback);
req.on('error', (e) => {
console.log('ERROOOO: ' + e.message);
});
req.write(postData);
req.end();

Percebeu que comeamos importando um mdulo novo?


O querystring ir servir para parsear os dados no formato Querystring, este sendo
nada mais que um padro que o protocolo HTTP utiliza para transporte de
informao do cliente para o servidor, ou at mesmo cliente - cliente.
O querystring obedece o seguinte padro:
nome_da_variavel=conteudo da variavel

Ento o contedo da const postData , aps a execuo da


querystring.stringify() , :

name=Jean%20Nascimento&type=professor

Depois criamos o, j manjado, JSON de configurao, porm dessa vez temos


mais coisas:

HTTP

43

Be MEAN - Node.js

const options = {
host: 'webschool-io.herokuapp.com'
, path: '/api/pokemons'
, headers: {
'Content-Type': 'application/x-www-form-urlencoded'
, 'Content-Length': postData.length
}
};

Dessa vez adicionamos o objeto headers o qual conter todos nossos


cabealhos, como:
{
'Content-Type': 'application/x-www-form-urlencoded'
, 'Content-Length': postData.length
}

Que so necessrios quando enviamos dados com POST .


O cabealho 'Content-Type': application/x-www-form-urlencoded diz a
forma que a informao enviada, nesse caso como querystring.
E o cabealho 'Content-Length': postData.length fala qual o tamanho,
em bytes, da informao enviada, aqui sendo 37 pois a quantidade de
caracteres de name=Jean%20Nascimento&type=professor , onde cada caracter
1 byte.

FIM
Dica: https://github.com/floatdrop/debug-http

HTTP

44

Be MEAN - Node.js

Callback
Olha s que legal, callback um termo bastante usado nas nossas aulas e
deveremos entender a sua razo de existencia. O node.js trabalha fortemente
com programao assncrona, I/O assncrono s code-nome.
Vamos mostrar isso em cdigo e explicar linha a linha.
/**
* Olha s
*/
function sayName (name, cb) {
if (typeof name === 'undefined') {

var error = new Error("Voc deve passar um parmetro como string para
/*** aqui a gente s retorna o erro da nossa funo */
return cb(err, null);
} else {

/*** agora definimos o nosso erro como null e retornamos o valor passad
return cb(null, name);
}
}

A ideia de callback justamente proporcionar uma extenso da continuao da


funo sayName, sem ter que precisar parar o cdigo para fazer isso em uma
outra funo.
Para extendermos a execuo de sayName, precisamos usar uma funo
annima e passarmos como parmetro, no segundo parmetro.
/*** a funo normal seria assim */
// sayName("Caio");
/*** funo com callback */
sayName ("Caio", function (err, result) {
if (err) console.log(err);
console.log(result); // "Caio"
});

Callbacks

45

Be MEAN - Node.js
Confesso para os senhores que um pouco verboso usar callback, mas s assim
conseguimos garantir a continuao da execuo de uma determinada funo.
O legal ter uma forma extendida de nossa funo.
Esse estilo de programao herdada do paradigma funcional que o javascript
sofre influncia, facilmente confudvel com o conceito de first-class function,
porm o que se adequa mais ao conceito de continuao de uma funo o
high-order-functions.
Essa continuao um pattern de programao funcional chamado de
continuation-passing-style. bom conhecermos esses nomes por que se a gente
quiser se aprofundar mais nos conceitos a gente sabe para onde ir :).
Nem tudo mar de flores, nem tudo to bonito assim.

Callback-hell ou boomerang-effect:
E se agente quiser tratar callbacks como um certa ordem? Vejamos o seguinte
exemplo com mais de uma funo definida

Callbacks

46

Be MEAN - Node.js

function sayName (name, cb) {


// dessa vez a gente vai usar um primito assincrono
// para garantir que essa funo seja assincrona de qualquer forma
setTimeout(function () {
if (typeof name === 'undefined') {

var error = new Error("Voc deve passar um parmetro como string pa


/*** aqui a gente s retorna o erro da nossa funo */
return cb(err, null);
} else {

/*** agora definimos o nosso erro como null e retornamos o valor pass
return cb(null, name);
}
}, 100);
}
function saySurname (name, cb) {
// dessa vez a gente vai usar um primito assincrono
// para garantir que essa funo seja assincrona de qualquer forma
setTimeout(function () {
if (typeof name === 'undefined') {

var error = new Error("Voc deve passar um parmetro como string pa


/*** aqui a gente s retorna o erro da nossa funo */
return cb(err, null);
} else {

/*** agora definimos o nosso erro como null e retornamos o valor pass
return cb(null, name);
}
}, 105);
}

E agora para darmos uma ordem de execuo desses callbacks:

Callbacks

47

Be MEAN - Node.js

sayName ("Caio", function (err, result) {


if (err) console.log(err);
console.log(result); // "Caio"
saySurname("Cutrim", function (err, result) {
if (err) console.log(err);
console.log(result); // "Cutrim"
});
});

Agora a gente tem uma pequena diferena quando colocamos um primitivo


assncrono chamado timer-function, isso deixa o nosso assncrono, ento se
assncrono a gente no tem ordem na execuo do nossos callbacks.
Voc j consegue imaginar que se quisermos ter mais funes assncronas a
gente vai ter mais callbacks aninhados no mesmo? ou seja:
sayName ("Caio", function (err, result) {
if (err) console.log(err);
console.log(result); // "Caio"
saySurname("Cutrim", function (err, result) {
if (err) console.log(err);
console.log(result); // "Cutrim"
sayFullName("Caio Cutrim", function (err, result) {
if (err) console.log(err);
console.log(result); // "Caio Cutrim"
});
});
});

Aff... a tendendncia desse cdigo ficar cada vez mais feia devido a
complexidade das funes que criarmos, ento para evitar isso ns temos varias
solues que ns iremos tratar posteriormente. :)
Beijo no corao.

Callbacks

48

Be MEAN - Node.js

FileSystem

FileSystem

49

Be MEAN - Node.js

NPM
O npm nada mais que o gerenciador de pacotes do Node.js.
Uns dizem que npm significa Node Package Manager, outros dizem que no,
ento FODA-SE. Ele nosso gerenciador dos mdulos que iremos instalar.
Se voc ver pelo site do npm, ele o gerenciador de pacotes do JavaScript.

O npm gerencia seu projeto a partir de um JSON chamado package.json e


ele muito importante e obrigatrio em todos seus projetos.
POR FAVOR NO ESQUEA DISSO!!!
no package.json onde esto todas as informaes do seu projeto, como
nome;
verso;
descrio;
autor;
licena;
dependncias;
outros.
O objeto de dependncias um dos mais importantes, pois voc nunca dever
enviar a pasta node_modules , a qual armazena todos os mdulos instalados no
seu projeto.
Se eu nunca devo enviar minhas dependncias ento como algum ir
instal-las?

npm

50

Be MEAN - Node.js
tima pergunta gafanhoto, por isso que esse array de dependncia to
importante, pois nele que voc ir especificar cada mdulo e sua verso.
Fazendo com que a listagem de todas as dependncias estejam no
package.json , quando algum clonar o projeto receber apenas os cdigos e

o package.json .
Com o package.json em mos, basta executar npm install para que o
npm instale todas aquelas dependncias listadas.
Simples no?
Por isso SEMPRE ADICIONE node_module no seu .gitignore!

Alm do npm ns temos dois gerenciadores de verses para o Node.js:


n
nvm

n
Para instalar o n bem fcil, basta clonar esse repositrio do TJ e depois
executar o make install :

npm

51

Be MEAN - Node.js

tmp git clone https://github.com/tj/n


Cloning into 'n'...
remote: Counting objects: 1322, done.
remote: Total 1322 (delta 0), reused 0 (delta 0), pack-reused 1322
Receiving objects: 100% (1322/1322), 218.22 KiB | 0 bytes/s, done.
Resolving deltas: 100% (471/471), done.
Checking connectivity... done.
tmp cd n
n git:(master) make install
mkdir -p /usr/local/bin/
cp bin/n /usr/local/bin/n
n git:(master) n latest
install : node-v5.2.0
mkdir : /usr/local/n/versions/node/5.2.0
fetch : https://nodejs.org/dist/v5.2.0/node-v5.2.0-darwin-x64.tar.gz

######################################################################## 100
...
installed : v5.2.0
n git:(master) node -v
v5.2.0
n git:(master) npm -v
3.3.12

nvm
Sobre o nvm eu j escrevi aqui no meu blog - Node.js - O que nvm e como
gerenciar verses do Node?.
Aconselho a leitura.
Agora vamos voltar ao npm.

npm init
npm

52

Be MEAN - Node.js
Quando iniciarmos qualquer projeto utilizando o Node.js, usaremos esse
comando npm init para inicializar o projeto.
Ento vamos iniciar nosso primeiro projeto utilizando os dados dos Pokemons,
para isso crie uma pasta chamada pokemons-api dentro da sua pasta do
Node.js do nosso workshop.

mkdir pokemons-api
cd pokemons-api
npm init

Depois basta responder as perguntas como abaixo:


pokemons-api npm init
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.
See `npm help json` for definitive documentation on these fields
and exactly what they do.
Use `npm install <pkg> --save` afterwards to install a package and
save it as a dependency in the package.json file.
Press ^C at any time to quit.
name: (pokemons-api)
version: (1.0.0)
description: Api dos pokemons
entry point: (index.js) server.js
test command:
git repository:
keywords: api, pokemons, node.js, mean
author: Suissa
license: (ISC) WTFPL

Depois da licena ele pergunta se voc confirma aqueles dados, basta apertar
Enter ou digitar sua licena, no meu caso a WTFPL.

Depois de confirmado ele mostra o package.json criado:

npm

53

Be MEAN - Node.js

{
"name": "pokemons-api",
"version": "1.0.0",
"description": "Api dos pokemons",
"main": "server.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [
"api",
"pokemons",
"node.js",
"mean"
],
"author": "Suissa",
"license": "WTFPL"
}

package.json
name
As coisas mais importantes no seu package.json so o nome e a verso, pois
eles so obrigatrios e seu package no instalar sem eles.
O nome e a verso, juntos, formam um identificador que para ser nico, no
esquea de mudar a verso quando soltar algum release novo.
Algumas regras:
o nome precisa ser menor que 214 caracteres;
o nome no pode comear com um . ou _ ;
novos packages no podem ter letras maisculas no nome;
o nome no pode conter quaisquer caracteres non-URL-safe.
Algumas dicas:
no use o mesmo nome de um mdulo do core do Node;
no coloque "js" ou "node" no nome;

npm

54

Be MEAN - Node.js
o nome provavelmente ser passado como um argumento para require(), por
isso deve ser algo curto, mas tambm razoavelmente descritivo;
voc pode querer verificar o registro do npm para ver se h algo com esse
nome j, antes que voc fique muito apegado a ele. https://www.npmjs.com/

npm install
O comando npm install serve para instalar algum mdulo/pacote ou as
dependncias listadas no package.json
Vamos conhecer um pouco mais sobre as opes do npm install .

npm install --global ou -g


Com o npm podemos instalar um mdulo de forma global ou local porm sendo
mdulos diferentes, pois um mdulo global tem a funo de ser executado em
linha de comando, enquanto que o local instalado para ser usado na
programao do nosso sistema.
Para instalar um mdulo globalmente usaremos npm install -g
nome_do_modulo .

Vamos aproveitar e instalar o gulp globalmente:


npm install -g gulp

Vrios mdulos que usamos direto iremos instalar globalmente como:


nodemon;
gulp;
mocha;
express-generator;
e outros.

Erro: EACCES

npm

55

Be MEAN - Node.js
Voc pode receber um erro EACCES quando voc tentar instalar um pacote
global. Isso indica que voc no tem permisso para gravar os diretrios que o
NPM usa para armazenar pacotes globais e comandos.
Voc pode corrigir esse problema usando uma das duas opes:
Alterar a permisso do diretrio padro do NPM.
Alterar o diretrio padro do npm para outro diretrio.
Opo 1: Mudar a permisso do diretrio padro do NPM
Primeiramente voc precisa saber qual o diretrio padro do NPM, para isso
execute npm config get prefix :
npm config get prefix
/usr/local

Se for um Sistema Operacional baseado em Unix provavelmente ser:


/usr/local .

Depois para mudar a permisso desse diretrio para que seu usurio seja o dono
dele basta executar sudo chown -R whoami diretorio
Caso voc no queira mudar a permisso de todo o diretrio, voc pode mudar
apenas as permisses dos sub-diretrios:
bin
share
lib/node_modules
Opo 2: Mudar o diretrio padro do NPM para outro
H momentos em que voc no deseja alterar a propriedade do diretrio padro
que o NPM usa; por exemplo, se voc estiver compartilhando uma mquina com
outros usurios.
Neste caso, voc pode configurar npm para usar um diretrio diferente.
Crie um diretrio para instalaes globais:

npm

56

Be MEAN - Node.js

mkdir ~/npm-global

Configure o NPM para usar o novo o diretrio:


npm config set prefix '~/npm-global'

Abra ou crie um arquivo ~/.profile e adicione esta linha:


export PATH=~/npm-global/bin:$PATH

Volte na linha de comando e atualize suas variveis do sistema com:


source ~/.profile

Caso voc no queira modificar seu ~/.profile , nos passos 2 a 4, poder


fazer assim:
NPM_CONFIG_PREFIX=~/npm-global npm install -g jshint

npm install --save ou -S


Com o -g voc instala os mdulos globalmente, agora para instalar o mdulo
localmente basta executar:
npm install nome_modulo

Instalando somente dessa forma voc no adiciona o mdulo instalado na lista de


dependncias do package.json, para adicionar nas listagem das dependncias
basta adicionar --save .
Ento dentro do diretrio da nossa aplicao pokemons-api vamos instalar o
mongoose pois precisaremos dele futuramente.

npm

57

Be MEAN - Node.js

npm i --save mongoose

Sim podemos usar apenas o i em vez do install !!!


Depois de instalar, para voc confirmar basta olhar seu package.json :
{
"name": "pokemons-api",
"version": "1.0.0",
"description": "Api dos pokemons",
"main": "server.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [
"api",
"pokemons",
"node.js",
"mean"
],
"author": "Suissa",
"license": "WTFPL",
"dependencies": {
"mongoose": "^4.3.3"
}
}

Perceba que ele criou um objeto novo chamado dependencies


"dependencies": {
"mongoose": "^4.3.3"
}

Caso deseje instalar uma verso especfica basta executar assim:

npm

58

Be MEAN - Node.js

npm i --save modulo@verso


// Exemplo
npm i --save mongoose@4.0

Voc deve ter percebido que existe um ^ antes da verso, correto?


Ento possumos algumas formas diferentes de especificar a verso do nosso
mdulo, que so:
~verso "Equivalente a verso"
^verso "Compativel com a verso"
verso Precisa ser a evrsao exata
verso Must be greater than verso
=verso etc
< verso
<=verso
1.2.x 1.2.0, 1.2.1, etc., mas no 1.3.0
Exemplo de como escolher uma faixa de verses:
npm i mongoose@">=4.1.0 <4.3.0"

Para voc instalar apenas as dependncias listadas em dependencies


basta executar npm install --production .

npm install --save-dev ou -D


Executando o npm install --save-dev ele ir adicionar seu mdulo e verso
no objeto devDependencies do package.json .
Para instalar apenas as devDependencies voc dever executar npm install
--dev , com isso ele no instalar as dependncias listadas em
dependencies .

npm install --optional ou -O


npm

59

Be MEAN - Node.js
Ir adicionar sua dependncia em optionalDependencies .
So dependncias opcionais que no devem interferir na execuo do projeto.

npm run
Esse um tpico deveras interessante, pois voc pode executar scripts para
automatizar suas tarefas.
Para demonstrar isso primeiro crie uma pasta chamada npm e dentro dela
execute o npm init , depois crie o script.js com esse pequeno cdigo:
console.log("Rodei!");

Depois de salvar o script.js adicione a seguinte linha:


"scripts": {
"roda": "node script.js"
},

Agora basta executar da seguinte forma:


npm run roda

> roda-script@1.0.0 roda /Users/jeancarlonascimento/www/projetos/webschool/c


> node script.js
Rodei!

Ento como visto acima o comando :


npm run nome-script

Alm de executar seus scripts ele tambm possui os seguintes scripts nativos:
prepublish: Roda ANTES do mdulo ser publicado. (Also run on local npm

npm

60

Be MEAN - Node.js
install without any arguments.);
publish: publica um mdulo no npm;
postpublish: Roda DEPOIS do mdulo ser publicado;
preinstall: Roda ANTES do mdulo ser instalado;
install: Instala todas as dependncias localmente;
postinstall: Roda DEPOIS do mdulo ser instalado;
preuninstall, uninstall: Roda ANTES the package ser desinstalado;
postuninstall: Roda DEPOIS the package is desinstalado;
preversion: Roda ANTES do comando version;
version: Execute para modificar sua verso;
postversion: Roda DEPOIS de rodar o version;
pretest: Roda ANTES do test;
test: Roda o comando que executa os testes;
posttest: Roda DEPOIS de executar os testes;
prestop: Roda ANTES do comando stop;
stop: Roda o script caso definido;
poststop: Roda DEPOIS do comando stop;
prestart: Roda ANTES do comando start;
start: Executa o comando definido, normalmente utilizado para levantar o
servidor;
poststart: Roda DEPOIS do comando start;
prerestart: Roda ANTES do comando restart;
restart: Reinicia o script;
postrestart: Roda DEPOIS do comando restart.
O restart na verdade executa o stop e o start tambm, e seus scripts
pre e post, na ordem abaixo:
prerestart
prestop
stop
poststop
restart
prestart
start
poststart
postrestart

npm

61

Be MEAN - Node.js
ps: Com os scripts nativos no necessrio npm run script, apenas npm script.

npm

62

Be MEAN - Node.js

Mongoose
O Mongoose um dos projetos mais utilizados quando trabalhamos com o
MongoDb pois ele nos d uma funcionalidade que o MongoDb no d
nativamente, voc sabe qual?

Ele nos proporciona SCHEMAS!!

Pois , isso para projetos mdios/grandes muito necessrio para padronizar


as coisas entre a equipe, caso voc seja um programador de backend e utiliza
algum banco relacional provavelmente j utilizou algum ORM (Object-relational
Mapper) da vida, o Mongoose um ODM (Object-document Mapper).
Antes de iniciarmos nossa jornada pelos campos verdejantes do Mongoose
precisamos conhecer alguns conceitos, no falaremos sobre plugins e
middlewares ainda.

Mongoose

63

Be MEAN - Node.js

Schema
Tudo no Mongoose comea com o Schema, ele o esqueleto da nossa coleo,
o objeto onde definimos cada campo da coleo com seu tipo, atributos e
validao.
Percebeu como importante?

Bom j que voc percebeu ento vamos ver como criar nosso primeiro Schema,
obviamente voc precisa importar o mdulo do mongoose antes:
const mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/be-mean-instagram');
const Schema = mongoose.Schema;
// Criao do Schema
const pokemonSchema = new Schema({
name: String,
description: String,
type: String,
attack: Number,
defense: Number,
height: Number,
});
// apenas para verificar a criao
console.log(pokemonSchema);

Antes de continuarmos a explicao de Schemas vamos entender um pouco


sobre os eventos e a conexo do Mongoose.

connect
Mongoose

64

Be MEAN - Node.js
Obviamente precisamos conectar no MongoDb antes de fazermos qualquer coisa
com o Mongoose e para isso tambm contamos com eventos para nos ajudar a
gerenciar essa conexo.
Primeiramente passamos a string de conexo para a funo connect :
const dbURI = 'mongodb://localhost/be-mean-instagram';
mongoose.connect(dbURI);

Para depois trabalharmos com esses 4 eventos:


mongoose.connection.on('connected', function () {
console.log('Mongoose default connection open to ' + dbURI);
});
mongoose.connection.on('error',function (err) {
console.log('Mongoose default connection error: ' + err);
});
mongoose.connection.on('disconnected', function () {
console.log('Mongoose default connection disconnected');
});
mongoose.connection.on('open', function () {
console.log('Mongoose default connection is open');
});

E para colocar a cereja no bolo, vamos fechar a conexo com o MongoDb caso o
processo do Node.js seja finalizado:
process.on('SIGINT', function() {
mongoose.connection.close(function () {

console.log('Mongoose default connection disconnected through app termin


process.exit(0);
});
});

Mongoose

65

Be MEAN - Node.js
Agora juntando tudo isso temos um arquivo de configurao/conexo com o
MongoDb que podde ser re-usado.
const dbURI = 'mongodb://localhost/be-mean-instagram';
mongoose.connect(dbURI);
mongoose.connection.on('connected', function () {
console.log('Mongoose default connection open to ' + dbURI);
});
mongoose.connection.on('error',function (err) {
console.log('Mongoose default connection error: ' + err);
});
mongoose.connection.on('disconnected', function () {
console.log('Mongoose default connection disconnected');
});
mongoose.connection.on('open', function () {
console.log('Mongoose default connection is open');
});
process.on('SIGINT', function() {
mongoose.connection.close(function () {

console.log('Mongoose default connection disconnected through app termin


process.exit(0);
});
});

Para saber mais visite a documentao.

Schema - continuao
No exemplo anterior criamos o Schema para nossa coleo de Pokemons que
criamos no mdulo de MongoDB, mas podemos melhorar ele deixando o JSON
de configurao do Schema separado da criao.

Mongoose

66

Be MEAN - Node.js

let mongoose = require('mongoose');


mongoose.connect('mongodb://localhost/be-mean-instagram');
let Schema = mongoose.Schema;
let _schema = {
name: String,
description: String,
type: String,
attack: Number,
defense: Number,
height: Number,
}
// Criao do Schema
let pokemonSchema = new Schema(_schema);
// apenas para verificar a criao
console.log(pokemonSchema);

J ficou bem melhor n? Mas e se quisermos ter algum valor por padro?
Para isso usaremos o default !

default
Vamos adicionar o campo created_at no nosso esquema com um valor
padro, mas voc s ver ele em ao quando inserir um objeto novo.

Mongoose

67

Be MEAN - Node.js

let mongoose = require('mongoose');


mongoose.connect('mongodb://localhost/be-mean-instagram');
let Schema = mongoose.Schema;
let _schema = {
name: String,
description: String,
type: String,
attack: Number,
defense: Number,
height: Number,
created_at: { type: Date, default: Date.now }
}
// Criao do Schema
let pokemonSchema = new Schema(_schema);
// apenas para verificar a criao
console.log(pokemonSchema);

Nesse caso quando um objeto for inserido ele colocar o valor da data atual no
campo created_at , o MongoDB ir persistir essa data no formato ISODate.
Calma que j veremos isso em ao antes precisamos aprender mais
algumas coisinhas.
Voc deve ter percebido que o campo created_at um objeto diferente dos
outros, mas por qu?
Basicamente porque quando passamos apenas o nome do tipo estamos
usando apenas o atributo type do objeto de configurao do campo, para
conhecermos mais sobre isso precisamos conhecer primeiramente quais os tipos
suportados pelo Schema.

Tipos
O Mongoose aceita vrios tipos diferentes, que so:
String
Number
Date

Mongoose

68

Be MEAN - Node.js
Buffer
Boolean
Mixed
Objectid
Array
Vamos falar brevemente sobre cada um, os que no tem link porque no tem
na documentao oficial.
Se voc entrar nos links ver que no tem nenhum tipo de explicao adicional
sobre cada tipo, apenas suas funcionalidades, ento vamos ver se eu posso
ajudar nisso.

String
Obviamente o tipo que aceita Strings, nesse caso ele ir converter o tipo
Number para uma String, porm olhe o que acontece quando voc tenta inserir
um valor de um objeto:
const mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/be-mean-instagram');
const Schema = mongoose.Schema;
const _schema = {
name: String
}
// Criao do Schema
const pokemonSchema = new Schema(_schema);
const data = {name: {teste: "Suissa"}}
const Model = mongoose.model('testepokemon', pokemonSchema);
const poke = new Model(data);
poke.save(function (err, data) {
if (err) return console.log('ERRO: ', err);
console.log('Inseriu: ', data)
})

Quando executar esse cdigo ele ir mostrar o seguinte erro:

Mongoose

69

Be MEAN - Node.js

ERRO: { [ValidationError: testepokemon validation failed]


message: 'testepokemon validation failed',
name: 'ValidationError',
errors:
{ name:

{ [CastError: Cast to String failed for value "[object Object]" at pat

message: 'Cast to String failed for value "[object Object]" at path


name: 'CastError',
kind: 'String',
value: [Object],
path: 'name',
reason: undefined } } }

No se preocupe em como inserir agora, pois j j chegaremos nisso.


Voc percebeu que ele j possui uma validao padro para os tipos, n?
Falaremos mais sobre validao dos campos j na sequncia dos tipos.

Number
O tipo Number aceita tanto nmeros negativos como positivos e tanto interos
como decimais e tambm aceita fraes!

Mongoose

70

Be MEAN - Node.js

const mongoose = require('mongoose');


mongoose.connect('mongodb://localhost/be-mean-instagram');
const Schema = mongoose.Schema;
const _schema = {
name: Number
}
// Criao do Schema
const pokemonSchema = new Schema(_schema);
const data = {name: 1/2}
const Model = mongoose.model('testepokemon', pokemonSchema);
const poke = new Model(data);
poke.save(function (err, data) {
if (err) return console.log('ERRO: ', err);
console.log('Inseriu: ', data)
})

Executando esse script voc ver a seguinte mensagem no terminal:


Inseriu: { _id: 5691c74c1349c94c148cd08a, name: 0.5, __v: 0 }

Claramente se a diviso funciona a multiplicao tambm, no preciso nem


mostrar n?

Mongoose

71

Be MEAN - Node.js

Date
Armazena datas no formato ISODate, vamos utilizar o cdigo j feito
anteriormente.
const mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/be-mean-instagram');
const Schema = mongoose.Schema;
const _schema = {
name: String,
description: String,
type: String,
attack: Number,
defense: Number,
height: Number,
created_at: { type: Date, default: Date.now }
}
// Criao do Schema
const pokemonSchema = new Schema(_schema);
// apenas para verificar a criao
console.log(pokemonSchema);

Agora eu lhe pergunto: por que usamos Date.now em vez de Date.now()


que nos retorna o timestamp atual?

Porque o Date.now uma funo que ir executar quando voc criar o


objeto, nesse caso ele ir executar quando voc criar o Model, se voc
usasse Date.now() todos os objetos teriam o mesmo valor.
Simples no?

Mongoose

72

Be MEAN - Node.js

Buffer
O tipo Buffer muito para salconst arquivos e retorn-los da forma que
conhecemos no Node.js, porm o MongoDB converte para Binary.
Dica: caso for graconst uma imagem, converta-a para base64.
const imageSchema = new Schema({
mime: String,
bin: Buffer
});

Boolean
O tipo Boolean todo mundo sabe como , correto?

Claro que sabe, se no souber da uma conferida nesse material ultra bsico que
criamos para o JS4Girls.
Ou seja, ele basicamente aceita apenas valores booleanos que podem ser:
true ou 1

Mongoose

73

Be MEAN - Node.js
false ou 0
Por exemplo nesse cdigo:
const mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/be-mean-instagram');
const Schema = mongoose.Schema;
const _schema = {
name: Boolean
}
// Criao do Schema
const pokemonSchema = new Schema(_schema);
const data = {name: 1}
const Model = mongoose.model('testepokemon', pokemonSchema);
const poke = new Model(data);
poke.save(function (err, data) {
if (err) return console.log('ERRO: ', err);
console.log('Inseriu: ', data)
})

Quando executado ir retornar a seguinte mensagem no terminal:


Inseriu: { _id: 5691d23e85e26411154c8d12, name: true, __v: 0 }

Ento perceba que ele converteu o 1 para true .


Ridicularmente simples n?

Mongoose

74

Be MEAN - Node.js

Mixed

Agora chegamos em um tipo altamente cabuloso!


Por que tio Suissa?
Pois ele virtualmente aceita qualquer tipo, do Mongoose, e podemos utilizar
diferentes tipos de uma s vez, por exemplo:

Mongoose

75

Be MEAN - Node.js

const mongoose = require('mongoose');


mongoose.connect('mongodb://localhost/be-mean-instagram');
const Schema = mongoose.Schema;
const _schema = {
attacks: Schema.Types.Mixed
}
// Criao do Schema
const pokemonSchema = new Schema(_schema);
const data = { attacks:
[
{ name: 'Soco na cara',
power: 9000,
order: 1,
active: true,
created_at: Date.now()
},
{ name: 'Soco no peito',
power: 9400,
order: 2,
active: false,
created_at: Date.now()
}
]
};
const Model = mongoose.model('testepokemon', pokemonSchema);
const poke = new Model(data);
poke.save(function (err, data) {
if (err) return console.log('ERRO: ', err);
console.log('Inseriu: ', data)
})

Executando esse cdigo voc receber a seguinte mensagem:

Mongoose

76

Be MEAN - Node.js

Inseriu: { _id: 5691d60743056d6e1566274e,


attacks:
[ { name: 'Soco na cara',
power: 9000,
order: 1,
active: true,
created_at: 1452398087679 },
{ name: 'Soco no peito',
power: 9400,
order: 2,
active: false,
created_at: 1452398087679 } ],
__v: 0 }

Percebeu que o tipo do campo agora Schema.Types.Mixed e no apenas


Mixed ?

S no me pergunte o porqu pois no encontrei essa informao em nenhum


lugar, caso voc saiba o porqu por favor adicione aqui.
Obrigado.
Por favor s no v usar esse tipo indiscriminadamente se no irei puxar
seu p a noite!

Mongoose

77

Be MEAN - Node.js

ObjectId

Esse tipo de campo importantssimo quando queremos fazer ligaes entre as


colees, pois com ele que definimos o tipo de campo que receber o ObjectID
de algum documento, podendo ser da prpria coleo ou outra, de preferncia
outra n queridinha(o).
Irei utilizar no exemplo o ObjectID criado no exemplo anterior:

Mongoose

78

Be MEAN - Node.js

const mongoose = require('mongoose');


mongoose.connect('mongodb://localhost/be-mean-instagram');
const Schema = mongoose.Schema;
const _schema = {
pokemons: [{type: Schema.Types.ObjectId, ref: 'testepokemons'}]
}
// Criao do Schema
const pokemonSchema = new Schema(_schema);
const data = {
pokemons: ['5691d60743056d6e1566274e']
};
const Model = mongoose.model('mypokemons', pokemonSchema);
const poke = new Model(data);
poke.save(function (err, data) {
if (err) return console.log('ERRO: ', err);
console.log('Inseriu: ', data)
})

Inseriu: { pokemons: [ 5691d60743056d6e1566274e ],


_id: 5691db690526e62d1671fdc2,
__v: 0 }

Depois conferindo no MongoDb como foi inserido:


db.mypokemons.find()
{
"_id": ObjectId("5691db690526e62d1671fdc2"),
"pokemons": [
ObjectId("5691d60743056d6e1566274e")
],
"__v": 0
}

E isso ser muito importante por causa de uma coisa chamada: populate.

Mongoose

79

Be MEAN - Node.js
O populate ser o responsvel por fazer a busca pelos _ids especificados no
campo com Schema.Types.ObjectId e como voc deve ter percebido tambm
usamos mais um atributo:
ref: 'testepokemons'

Pois com o valor de ref , que o nome da coleo que possui aquele
documento, que o Mongoose ir fazer a busca nessa coleo, retornando o
resultado j adicionado no objeto de resposta, iremos ver melhor sobre isso
futuramente.
Mas lembre-se:
muito importante!

Array
Obviamente o tipo que aceita apenas array correto?

Mongoose

80

Be MEAN - Node.js

Mas como assim tio Suissa?


Vou mostrar com cdigo que ficar mais fcil, vamos utilizar o cdigo abaixo:
const mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/be-mean-instagram');
const Schema = mongoose.Schema;
const _schema = {
pokemons: Schema.Types.Array
}
const pokemonSchema = new Schema(_schema);
const data = {
pokemons: ['Pikachu', 'Squirtle']
};
const Model = mongoose.model('mypokemons', pokemonSchema);
const poke = new Model(data);
poke.save(function (err, data) {
if (err) return console.log('ERRO: ', err);
console.log('Inseriu: ', data)
})

Depois de executado voc receber a seguinte resposta:

Mongoose

81

Be MEAN - Node.js

Inseriu: { pokemons: [ [ 'Pikachu' ], [ 'Squirtle' ] ],


_id: 5691e6c10f9e77c316c518f2,
__v: 0 }

Isso acontece porque definimos pokemons: Schema.Types.Array e com isso


ele ir gerar um array para cada valor passado nesse campo, para "corrigirmos"
isso precisamos criar o Schema dessa forma:
const mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/be-mean-instagram');
const Schema = mongoose.Schema;
const _schema = {
pokemons: [String]
}
const pokemonSchema = new Schema(_schema);
const data = {
pokemons: ['Pikachu', 'Squirtle']
};
const Model = mongoose.model('mypokemons', pokemonSchema);
const poke = new Model(data);
poke.save(function (err, data) {
if (err) return console.log('ERRO: ', err);
console.log('Inseriu: ', data)
})

Mongoose

82

Be MEAN - Node.js
Executando...
Inseriu: { pokemons: [ 'Pikachu', 'Squirtle' ],
_id: 5691ea660fc87d1317e5d91f,
__v: 0 }

Percebeu ento que apenas mudei para pokemons: [String] pois nesse caso
o campo pokemons ir receber um array de Strings como seria o procedimento
mais natural.
Ento agora voc sabe que o tipo Schema.Types.Array cria um array para
cada elemento contido no campo.
Ento eu aconselho a voc usar a segunda forma que utilizando o Array do
JavaScript mesmo, alis dificilmente voc encontrar cdigos com
Schema.Types.Array mas eu tinha que explicar. :p

__v
Com certeza voc percebeu que quando inserimos algum documento o
Mongoose nos retorna o objeto com um atributo que no inserimos, o _v .
Tudo bem, mas para que serve essa biroska?

Mongoose

83

Be MEAN - Node.js
Esse campo adicionado automaticamente pelo Mongoose quando inserimos
algum documento novo, ele serve para o Mongoose gerenciar, internamente, a
verso de cada documento caso haja alguma alterao concorrente.
Caso necessrio voc pode modificar sua verso manualmente, se desejar
utilizar esse campo como versionador tambm.

Validation
Agora sim chegamos em algo de extrema importncia, a validao dos campos.

Antes de entrarmos em suas especificidades, vamos conhecer algumas regras:


Validao definida no tipo do campo, no Schema;
Validao uma pea interna do Middleware;
Validao ocorre quando um documento tenta ser salvo, aps ter sido
definido com seu padro;
Validadores no so executados em valores indefinidos. A nica exceo a
validao required;
Validao assincronamente recursiva, quando voc chamar a funo
save do Model, a validao dos sub-documentos executado tambm. Se

ocorrer um erro ele ser enviado para o callback da funo save ;


Validao suporta a personalizao completa.

Validao padro
Como j vimos anteriormente o Mongoose possui validaes padro para alguns
tipos de campos, alm disso todos os tipos tambm possui a validao de
required .

Mongoose

84

Be MEAN - Node.js
Porm alguns tipos possuem validadores mais especficos como:
Number: possui os validadores de max e min
String: possui os validadores de enum , match , maxlength e
minlength

Sabendo disso vamos analisar um erro j demonstrado anteriormente com o tipo


String quando tenta-se inserir um tipo Array nesse campo.
ERRO: { [ValidationError: testepokemon validation failed]
message: 'testepokemon validation failed',
name: 'ValidationError',
errors:
{ name:

{ [CastError: Cast to String failed for value "[object Object]" at pat

message: 'Cast to String failed for value "[object Object]" at path


name: 'CastError',
kind: 'String',
value: [Object],
path: 'name',
reason: undefined } } }

Vamos analisar esse objeto de retorno do Mongoose parte por parte.


ERRO: { [ValidationError: testepokemon validation failed] ...}

Acima podermos ver qual foi o erro, porm no fazemos nada com essa
informao, por nossa sorte ela vem separadinha logo abaixo:
{ [ValidationError: testepokemon validation failed]
message: 'testepokemon validation failed',
name: 'ValidationError'
...
}

Mongoose

85

Be MEAN - Node.js
Ento podemos perceber que a mensagem de erro contida em message
composta pelo nome do Model que deu a merda, testepokemon , mais
validation failed e no campo name o nome da validao que deu

errado, no caso ValidationError .


Logo aps chegamos no objeto mais importante, errors :
ERRO: { ...
errors:
{ name:

{ [CastError: Cast to String failed for value "[object Object]" at pat

message: 'Cast to String failed for value "[object Object]" at path


name: 'CastError',
kind: 'String',
value: [Object],
path: 'name',
reason: undefined } } }

Dentro do objeto errors existe um atributo com o nome do campo, que gerou
o erro, e atrelado a ele o seu objeto do erro.
{ name:
{ [CastError: Cast to String failed for value "[object Object]" at path

message: 'Cast to String failed for value "[object Object]" at path "nam
name: 'CastError',
kind: 'String',
value: [Object],
path: 'name',
reason: undefined
}
}

Sendo essa a estrutura padro para os erros da validao do Mongoose:

Mongoose

86

Be MEAN - Node.js

{ message: 'Cast to String failed for value "[object Object]" at path "name"
name: 'CastError',
kind: 'String',
value: [Object],
path: 'name',
reason: undefined
}

Analisando cada atributo ns temos:


message: texto da mensagem de erro;
name: nome do erro;
kind: tipo do campo;
value: valor que provocou o erro;
path: nome do campo;
reason: razo porque o erro ocorreu, raramente usado.
Porm nesse caso estamos mostrando apenas 1 erro, do campo name .
E se tivermos mais erros como ficar?
Vou mostrar para voc com um exemplo:

Mongoose

87

Be MEAN - Node.js

ERRO: { [ValidationError: testepokemon validation failed]


message: 'testepokemon validation failed',
name: 'ValidationError',
errors:
{ age:
{ [CastError: Cast to Number failed for value "bazinga" at path
message: 'Cast to Number failed for value "bazinga" at path "age"'
name: 'CastError',
kind: 'Number',
value: 'bazinga',
path: 'age',
reason: undefined },
name:
{ [CastError: Cast to String failed for value "[object Object]"

message: 'Cast to String failed for value "[object Object]" at path


name: 'CastError',
kind: 'String',
value: [Object],
path: 'name',
reason: undefined } } }

Interessante que mesmo com mais de 1 erro o objeto errors no convertido


para Array, mas sim ter o erro de cada campo como um objeto interno sendo
identificado pelo seu nome.

Validao customizada
Para criar uma validao customizada bem simples, basta passar um objeto
para o atributo validate do seu campo, no Schema:

Mongoose

88

Be MEAN - Node.js

age: {
type: Number,
validate: {
validator: function(v) {
return v >= 18;
},
message: 'Sua idade({VALUE}) no permitida!'
}
}

Aposto que voc imagina para que essa validao serve n?

Validadores sempre recebem o valor para validar como seu primeiro argumento e
devem retornar um valor booleano. Retornando false significa que a
validao falhou.
Vamos testar a validao:
const User = mongoose.model('user', userSchema);
const u = new User();
u.age = 24;
console.log(u.validateSync());
u.age = 6;
console.log(u.validateSync().toString());
u.age = 2;
console.log(u.validateSync());

Mongoose

89

Be MEAN - Node.js
Executando essa validao temos:
undefined
ValidationError: Sua idade(6) no permitida!
{ [ValidationError: user validation failed]
message: 'user validation failed',
name: 'ValidationError',
errors:
{ age:
{ [ValidatorError: Sua idade(2) no permitida!]
properties: [Object],
message: 'Sua idade(2) no permitida!',
name: 'ValidatorError',
kind: 'user defined',
path: 'age',
value: 2 } } }

Percebeu ento que undefined o retorno de uma validao de sucesso e


logo abaixo temos apenas a mensagem de erro que vem de
u.validateSync().toString() e por ltimo objeto de erro que j

conhecemos.
Agora vamos tentar validateSync().toString() com um valor maior que 18:
u.age = 69;
console.log(u.validateSync().toString());

E o resultado esse erro:

Mongoose

90

Be MEAN - Node.js

console.log(u.validateSync().toString());
^
TypeError: Cannot read property 'toString' of undefined

at Object.<anonymous> (/Users/jeancarlonascimento/www/projetos/webschool
at Module._compile (module.js:399:26)
at Object.Module._extensions..js (module.js:406:10)
at Module.load (module.js:345:32)
at Function.Module._load (module.js:302:12)
at Function.Module.runMain (module.js:431:10)
at startup (node.js:141:18)
at node.js:977:3

Com isso conseguimos deduzir que a funo toString no existe em


undefined e podemos proconst isso indo no console do node, para isso basta
executar node no seu terminal:
~ node
> undefined.toString()
TypeError: Cannot read property 'toString' of undefined
at repl:1:10
at REPLServer.defaultEval (repl.js:252:27)
at bound (domain.js:281:14)
at REPLServer.runBound [as eval] (domain.js:294:12)
at REPLServer.<anonymous> (repl.js:417:12)
at emitOne (events.js:83:20)
at REPLServer.emit (events.js:170:7)
at REPLServer.Interface._onLine (readline.js:211:10)
at REPLServer.Interface._line (readline.js:550:8)
at REPLServer.Interface._ttyWrite (readline.js:827:14)
> "Suissa".toString()
'Suissa'

Por isso cuidado ao usar essa funo, tenha certeza que esteja executando
em um erro!
Vamos criar uma validao um pouco mais complexa agora:

Mongoose

91

Be MEAN - Node.js

const mongoose = require('mongoose');


const Schema = mongoose.Schema;
const validateEmail = function(email) {
const re = /^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/;
return re.test(email)
};
const EmailSchema = new Schema({
email: {
type: String
, trim: true
, unique: true
, required: 'Email obrigatrio'
, validate: [validateEmail, 'Preencha com um email vlido']
}
});
const Email = mongoose.model('Email', EmailSchema);
const mail = new Email({email: "suissera@webschool.io"});
console.log(mail.validateSync());

Tambm tem a forma simples de testar regex com validate:


const userSchema = new Schema({
email: {
type: String,
validate: /^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/
}
});

Alm do match :

Mongoose

92

Be MEAN - Node.js

const userSchema = new Schema({


email: {
type: String,
match: [
/^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/
, 'Preencha com um email vlido'
]
}
});

Existe mais uma forma de utilizar a validao com Mongoose, utilizando o


Model.schema.path('campo') , passando uma funo e a mensagem de erro

para o validate :
const RequisitosSchema = new Schema({
name: String
});
const Requisitos = mongoose.model('Requisitos', RequisitosSchema);
Requisitos.schema.path('name').validate(function (value) {
return /js|html|css|angular|node|mongodb/i.test(value);
}, 'Requisito({VALUE}) invlido!');
const req = new Requisitos({ name: 'php'});
console.log(req.validateSync());

Model
O Model a implementao do Schema, sendo o objeto com o qual trabalhamos.
const Model = mongoose.model('Model', schema);

Para trabalhar com o Model iremos instanciar um documento para isso:

Mongoose

93

Be MEAN - Node.js

const _schema = {
name: String
}
const pokemonSchema = new Schema(_schema);
const PokemonModel = mongoose.model('Pokemon', pokemonSchema);
const Suissamon = new PokemonModel({ name: 'Suissamon' });
Suissamon.save(function (err, data) {
if (err) return console.log('ERRO: ', err);
return console.log('Inseriu:', data);
});
// ou
Suissamon.create({ name: 'Suissamon' }, function (err, data) {
if (err) return console.log('ERRO: ', err);
return console.log('Inseriu:', data);
});

Create - save()
J vimos em outros exemplos como criar um documento novo, ento agora
vamos padronizar seu uso.
const _schema = {
name: String
}
const pokemonSchema = new Schema(_schema);
const PokemonModel = mongoose.model('Pokemon', pokemonSchema);
const dataModel = { name: 'Suissamon' };
const Suissamon = new PokemonModel(dataModel);
Suissamon.save(function (err, data) {
if (err) return console.log('ERRO: ', err);
return console.log('Inseriu:', data);
});

Mongoose

94

Be MEAN - Node.js
Iremos sempre separar o JSON com os dados do Model( dataModel ) da sua
criao new PokemonModel(dataModel) para depois executar a funo
save , passando como parmetro uma funo de callback que ir sempre

receber 2 parmetros nessa ordem: erro(err) e retorno(data).

Retrieve - find()
Existem 2 formas diferentes de executar uma busca com o Mongoose:
via callback com JSON
encadeando funes
Vamos aprender das 2 formas, ento vamos buscar por exemplo o Pikachu
que foi inserido em exerccios anteriores, buscando pelo name e se o attack
for maior que 90 :
const pokemonSchema = new Schema(_schema);
const PokemonModel = mongoose.model('Pokemon', pokemonSchema);
const query = {name: 'Pikachu', attack: {'$gt': 90}};
PokemonModel.find(query, function (err, data) {
if (err) return console.log('ERRO: ', err);
return console.log('Buscou:', data);
});

Ento perceba que agora utilizamos diretamente o Model PokemonModel para


executar a funo find , passando como parmetros a query e o callback.
Nesse caso a query um JSON que usa a mesma sintaxe do cliente do
MongoDb.
Agora vamos ver da outra forma:

Mongoose

95

Be MEAN - Node.js

const pokemonSchema = new Schema(_schema);


const PokemonModel = mongoose.model('Pokemon', pokemonSchema);
const query = {name: 'Pikachu'};
const callback = function (err, data) {
if (err) return console.log('ERRO: ', err);
return console.log('Buscou:', data);
};
PokemonModel.find(query).where({attack: {$gt: 90}}).exec(callback);

Caso voc queira limitar quais campos devem ser retornados basta passar como
JSON no segundo parmetro, assim:
const pokemonSchema = new Schema(_schema);
const PokemonModel = mongoose.model('Pokemon', pokemonSchema);
const query = {name: 'Pikachu', attack: {'$gt': 90}};
const fields = {name: 1};
PokemonModel.find(query, fields, function (err, data) {
if (err) return console.log('ERRO: ', err);
return console.log('Buscou:', data);
});

Para voc fazer uma busca independente de maiscula ou minscula voc deve
usar de RegEx para isso:

Mongoose

96

Be MEAN - Node.js

const pokemonSchema = new Schema(_schema);


const PokemonModel = mongoose.model('Pokemon', pokemonSchema);
const query = {name: /pikachu/i};
PokemonModel.find(query, function (err, data) {
if (err) return console.log('ERRO: ', err);
return console.log('Buscou:', data);
});

Voc pode ver todas as funes dessa segunda forma aqui na documentao
oficial.

findOne
Como voc deve lembrar que alm do find tambm temos o findOne que
serve para qu?

Isso mesmo! Para buscar apenas 1 documento.


PokemonModel.findOne(query, function (err, data) {});
PokemonModel.findOne(query).exec(callback);

findById
O findById equivalente ao findOne({_id: id}) , com valor
findById(undefined) ele converte para findById({ _id: null }) .

Mongoose

97

Be MEAN - Node.js

PokemonModel.findById(id, function (err, data) {});


PokemonModel.findById(id).exec(callback);

Vamos buscar o Pikachu porm pelo seu _id :


const pokemonSchema = new Schema(_schema);
const PokemonModel = mongoose.model('Pokemon', pokemonSchema);
const id = '564220f0613f89ac53a7b5d0';
PokemonModel.findById(id, function (err, data) {
if (err) return console.log('ERRO: ', err);
return console.log('Buscou:', data);
})

Update - update()
Para alterarmos um documento iremos seguir a mesma lgica do update do
cliente do MongoDb, porm no Mongoose podemos omitir o operador $set
pois ele no ir sobrescrever todo seu objeto caso no o use, assim como no
cliente.
Lembra-se?

Vamos ento reaproveitar a query passada e mudar o attack do Pikachu


para 666 :

Mongoose

98

Be MEAN - Node.js

const pokemonSchema = new Schema(_schema);


const PokemonModel = mongoose.model('Pokemon', pokemonSchema);
const query = {name: /pikachu/i};
const mod = {attack: 666};
PokemonModel.where(query).update(mod).exec(function (err, data) {
if (err) return console.log('ERRO: ', err);
return console.log('Alterou:', data);
});

findAndModify
const pokemonSchema = new Schema(_schema);
const PokemonModel = mongoose.model('Pokemon', pokemonSchema);
const query = {name: /pikachu/i};
const mod = {attack: 666};
PokemonModel.findAndModify(query, mod, function (err, data) {
if (err) return console.log('ERRO: ', err);
return console.log('Alterou:', data);
})

findOneAndUpdate
upsert
multi

Delete - remove()
findOneAndRemove
Mongoose

99

Be MEAN - Node.js

Indexes
Quando o aplicativo iniciado, Mongoose chama automaticamente
ensureIndex para cada ndice definido no seu Schema. Mongoose chamar
ensureIndex sequencialmente para cada ndice, e emitem um evento index

no Model quando todas as chamadas de ensureIndex sejam sucesso ou


quando houver um erro.
var userSchema = new Schema({
name: String,
email: String,
date_created: { type: Date, , default: Date.now, index: true }
});
userSchema.index({ name: 1, type: -1 });

Recomenda-se que seja desativado em produo, a criao do ndice pode


causar um impacto significativo no desempenho. Desativar o comportamento,
definindo a opo autoIndex do seu Schema para false , ou globalmente na
conexo, definindo a opo config.autoIndex como false .
userSchema.set('autoIndex', false);
// or
new Schema({..}, { autoIndex: false });

Mas ele s impacta quando voc levanta seu sistema, sabendo disso voc no
precisa seguir essa recomendao de desligar o autoIndex , pois ele ir
garantir certa integridade dos seus dados.

Mongoose

100

Be MEAN - Node.js

const userSchema = new Schema({


name: String
, email: { type: String, unique: true }
, date_created: { type: Date, default: Date.now, index: true }
});
userSchema.index({ name: 1, date_created: -1 });
const User = mongoose.model('User', userSchema);

User.create({name: 'suissa', email: 'suissa@webschool.io'}, (err, data) => {


if (err) return console.log('Erro:', err);
return console.log('Animal: ', data);
});

Basta pesquisar na database test e na coleo system.indexes que iremos


encontrar a seguinte parte da busca:

Mongoose

101

Be MEAN - Node.js

test> db.system.indexes.find()
{
"v": 1,
"key": {
"_id": 1
},
"name": "_id_",
"ns": "test.users"
}
{
"v": 1,
"unique": true,
"key": {
"email": 1
},
"name": "email_1",
"ns": "test.users",
"background": true
}
{
"v": 1,
"key": {
"date_created": 1
},
"name": "date_created_1",
"ns": "test.users",
"background": true
}

Para criar um ndice composto basta faz-lo passando o JSON para a funo
index do Schema:

Mongoose

102

Be MEAN - Node.js

const userSchema = new Schema({


name: String
, email: { type: String, unique: true }
, date_created: { type: String, default: Date.now, index: true }
});
userSchema.index({ name: 1, date_created: -1 });

Buscando em system.indexes achamos nosso ndice:


{
"v": 1,
"key": {
"name": 1,
"date_created": -1
},
"name": "name_1_date_created_-1",
"ns": "test.users",
"background": true
}

Methods e Statics
No Mongoose podemos definir mtodos especficos para um Schema, como
tambm mtodos "estticos".

Methods
Para se definir um mtodo muito simples, basta criarmos ele no objeto
Schema.methods :

Mongoose

103

Be MEAN - Node.js

const _schema = {
name: String
, description: String
, type: String
, attack: Number
, defense: Number
, height: Number
};
const PokemonSchema = new Schema(_schema);
PokemonSchema.methods.findSimilarType = function findSimilarType (cb
return this.model('Pokemon').find({ type: this.type }, cb);
};
const Pokemon = mongoose.model('Pokemon', PokemonSchema);
const poke = new Pokemon({ name: 'Teste', type: 'inseto' });
poke.findSimilarType(function (err, data) {
if (err) return console.log('Erro:', err);
return data.forEach((pokemon) => console.log('pokemon: ', pokemon));
})

Como ns retornamos o find , que uma instncia de Query, na funo


findSimilarType podemos escrever a busca dessa forma:

poke
.findSimilarType()
.where('defense').gt(50)
.limit(2)
.exec(function (err, data) {
if (err) return console.log('Erro:', err);
return data.forEach((pokemon) => console.log('pokemon: ', pokemon));
});

Statics

Mongoose

104

Be MEAN - Node.js
Alm dos mtodos normais tambm podemos criar os mtodos estticos, os
quais sempre estaro acessveis no seu Model.
const _schema = {
name: String
, description: String
, type: String
, attack: Number
, defense: Number
, height: Number
};
const PokemonSchema = new Schema(_schema);
PokemonSchema.statics.search = function (name, cb) {
return this.where('name', new RegExp(name, 'i')).exec(cb);
};
const Pokemon = mongoose.model('Pokemon', PokemonSchema);
Pokemon.search('caterpie', function (err, data) {
if (err) return console.log('Erro:', err);
return data.forEach((pokemon) => console.log('pokemon: ', pokemon));
});

O que fazemos na funo search receber um nome e depois retornamos um


find implcito pois usamos o where para testar o valor de name com uma

expresso regular gerada pela funo RegExp, finalizando com a execuo do


callback cb .

Getters e Setters
Getters e setters ajudam a mudar a forma como voc obtm e/ou define os
atributos do documento.

Setters

Mongoose

105

Be MEAN - Node.js
Setters permitem que voc transforme os dados originais antes que cheguem ao
documento.
Suponha que voc est implementando o registro do usurio para um site.
Usurio fornecer um e-mail e senha, que fica guardado no MongoDB. O e-mail
uma seqncia de caracteres que voc vai querer normalizar para minsculas.
Voc pode configurar a normalizao do e-mail para minsculas facilmente
atravs de um setter.
function toLower (v) {
return v.toLowerCase();
}
const UserSchema = new Schema({
email: { type: String, set: toLower }
});
const User = mongoose.model('User', UserSchema);
const user = new User({email: 'SUISSERA@webschool.io'});
console.log(user.email); // 'suissera@webschool.io'

Getters
Getters permitem que voc transforme a representao dos dados, uma vez que
transformado a partir do documento para o valor que voc v.
Suponha que voc queira retornar o ttulo do post todo em maiscula.
Voc pode faz-lo atravs da definio de um getter.

Mongoose

106

Be MEAN - Node.js

function apenasMaiusculas (v) {


return v.toUpperCase();
};
const CommentsSchema = new Schema({
title: String
, body: String
, date: Date
});
const BlogPostSchema = new Schema({
title: { type: String, get: apenasMaiusculas }
, body: String
, comments: [CommentsSchema]
});
const BlogPostModel = mongoose.model('BlogPost', BlogPostSchema);
const post_id = '569e36b2d6a928b526db9135';
BlogPostModel.findById(post_id, function (err, post) {
if (err) return console.log('Erro:', err);
return console.log('Ttulo: ', post.title);
});

Virtuals
O Mongoose suporta atributos virtuais, que so convenientes em alguns
momentos, mas no so armazenados no MongoDB.
Pense no seguinte Schema:
const PersonSchema = new Schema({
name: {
first: String
, last: String
}
});

Mongoose

107

Be MEAN - Node.js
Se voc deseja mostrar os valores dos virtuals no cliente deve setar {
virtuals: true } para toObject e toJSON no Schema, como mostrado

abaixo:
const PersonSchema = new Schema({
name: {
first: String
, last: String
}
}, {
toObject: {
virtuals: true
},
toJSON: {
virtuals: true
}
});

Depois defina o nome:


const Person = mongoose.model('Person', PersonSchema);
const Suissao = new Person({
name: { first: 'Jean', last: 'Suissa' }
});

Se voc quiser mostrar o nome completo ter que fazer:


console.log(Suissao.name.first + ' ' + Suissao.name.last);

mais conveniente definir um atributo virtual name.full e escrever dessa


forma:
console.log(Suissao.name.full);

Mongoose

108

Be MEAN - Node.js
Para fazer isso basta passar 'name.full' para a funo virtual do
Schema:
PersonSchema
.virtual('name.full')
.get(function () {
return this.name.first + ' ' + this.name.last;
});

Cadastre uma Person nova:


Person.create({ name: { first: 'Jean', last: 'Suissa' }}, (err, data) => {
if (err) return console.log('Erro:', err);
return console.log('Cadastrou: ', data);
});

ps: Fiz com o create para economizar cdigo.


Agora buscando o Person para verificar seu nome completo:
Person.findById('569e513f7672012c28da89f1', (err, data) => {
if (err) return console.log('Erro:', err);
return console.log('Nome completo: ', data.name.full);
});

Retornando:
Nome completo: Jean Suissa

Vamos fazer um outro campo virtual que ir retornar apenas as iniciais de


Person :

Mongoose

109

Be MEAN - Node.js

PersonSchema
.virtual('name.initials')
.get(function () {
return this.name.first[0] + this.name.last[0];
});
const Person = mongoose.model('Person', PersonSchema);
Person.findById('569e513f7672012c28da89f1', (err, data) => {
if (err) return console.log('Erro:', err);
return console.log('Iniciais: ', data.name.initials);
});

E nossa resposta :
Iniciais: JS

Embedded Documents
Esse tpico muito interessante pois diversas vezes iremos colocar um
documento dentro de outro(embedded). Documentos incorporados desfrutam dos
mesmos recursos que os Models. Sempre que ocorrer um erro ele ir para o
callback do save() .

Adicionando
Vamos iniciar com um exemplo clssico, de Blog:

Mongoose

110

Be MEAN - Node.js

const CommentsSchema = new Schema({


title: String,
body: String,
date: Date
});
const BlogPostSchema = new Schema({
title: String,
body: String,
comments: [CommentsSchema]
});
const BlogPostModel = mongoose.model('BlogPost', BlogPostSchema);

O atributo comments em BlogPostSchema ser uma instncia de


DocumentArray , que um subclasse especial de Array que possui mtodos

especficos para trabalhar co documentos incorporados.


const BlogPostModel = mongoose.model('BlogPost', BlogPostSchema);
const BlogPost = new BlogPostModel();
const comment = {
title: 'Comentei aqui'
, body: 'T comentando meu fiiiii!'
, date: Date.now()
};
BlogPost.comments.push(comment);
BlogPost.save(function (err, data) {
if (err) return console.log('Erro:', err);
return console.log('Sucesso:', data);
});

Quando executamos esse cdigo recebemos a seguinte mensagem:

Mongoose

111

Be MEAN - Node.js

Sucesso: { comments:
[ { _id: 569e2ef6e17e3736266c9cd7,
date: Tue Jan 19 2016 10:41:26 GMT-0200 (BRST),
body: 'T comentando meu fiiiii!',
title: 'Comentei aqui' } ],
_id: 569e2ef6e17e3736266c9cd6,
__v: 0 }

No ficou muito claro? Ento vamos colocar os valores do Post.


const post = {
title: 'Primeiro POST'
, body: 'Post inicial do blog UEBAAA'
, date: Date.now()
}
const comment = {
title: 'Comentei aqui'
, body: 'T comentando meu fiiiii!'
, date: Date.now()
};
const BlogPostModel = mongoose.model('BlogPost', BlogPostSchema);
const BlogPost = new BlogPostModel(post);
BlogPost.comments.push(comment);
BlogPost.save(function (err, data) {
if (err) return console.log('Erro:', err);
return console.log('Sucesso:', data);
});

Como resultado recebemos:

Mongoose

112

Be MEAN - Node.js

Sucesso: { comments:
[ { _id: 569e300ad1455a8326c9aa92,
date: Tue Jan 19 2016 10:46:02 GMT-0200 (BRST),
body: 'T comentando meu fiiiii!',
title: 'Outro comentrio' } ],
_id: 569e300ad1455a8326c9aa91,
body: 'Post inicial do blog UEBAAA',
title: 'Primeiro POST',
__v: 0 }

Removendo
Para remover um documento incorporado precisamos primeiramente buscar o
documento "pai", pelo _id de preferncia, para depois selecionar qual
documento interno deve ser removido e depois salconst o documento modificado.
const BlogPostModel = mongoose.model('BlogPost', BlogPostSchema);
const BlogPost = new BlogPostModel(post);
const id = '569e300ad1455a8326c9aa91';
BlogPostModel.findById(id, function (err, post) {
if (err) return console.log('Erro:', err);
console.log('post.comments', post.comments)
post.comments[0].remove();
post.save(function (err, data) {
if (err) return console.log('Erro interno:', err);
return console.log('Sucesso:', data);
});
});

Como resultado recebos a seguinte mensagem no terminal:

Mongoose

113

Be MEAN - Node.js

post.comments [{ title: 'Outro comentrio',


body: 'T comentando meu fiiiii!',
date: Tue Jan 19 2016 10:46:02 GMT-0200 (BRST),
_id: 569e300ad1455a8326c9aa92 }]
Sucesso: { comments: [],
__v: 1,
body: 'Post inicial do blog UEBAAA',
title: 'Primeiro POST',
_id: 569e300ad1455a8326c9aa91 }

Procurando pelo _id


O tipo DocumentArray possui o mtodo especial id() o qual filtra os
documentos incorporados pelo seu atributo _id .
Vamo inserir novamente o comentrio e depois buscar pelo seu _id .
const BlogPostModel = mongoose.model('BlogPost', BlogPostSchema);
const BlogPost = new BlogPostModel(post);
const post_id = '569e36b2d6a928b526db9135';
const comment_id = '569e36b2d6a928b526db9136';
BlogPostModel.findById(post_id, function (err, post) {
if (err) return console.log('Erro:', err);
console.log('Achou esse comentrio: ', post.comments.id(comment_id));
});

Executando, recebemos:
Achou esse comentrio: { title: 'Outro comentrio',
body: 'T comentando meu fiiiii!',
date: Tue Jan 19 2016 11:14:26 GMT-0200 (BRST),
_id: 569e36b2d6a928b526db9136 }

Juntando tudo
Mongoose

114

Be MEAN - Node.js
Vamos agora criar um Schema mais completo e atmico com o conhecimento
adquirido.
J conhecemos o conceito de Arquitetura Atmica para o Mongoose, ento
vamos criar um Schema de usurio, primeiramente da forma simples:
const userSchema = new Schema({
name: String
, password: String
, email: String
, type: String
, created_at: { type: Date, default: Date.now }
});

Antes de tudo vamos criar um projeto novo chamado mongoose-user via npm
init , depois instalando localmente o mongoose vamos copiar a pasta
fields do projeto mongoose-atomic e colar na pasta do projeto mongooseuser , para podermos reaproveitar o cdigo criado anteriormente.

Agora salve o cdigo abaixo como schema.js na pasta do mongoose-user :


const mongoose = require('mongoose');
const Schema = mongoose.Schema;
mongoose.connect('mongodb://localhost/mongoose-user-test');
const userSchema = new Schema({
name: String
, password: String
, email: String
, type: String
, created_at: { type: Date, default: Date.now }
});

Depois disso vamos atomizar nosso Schema re-usando os fields:

Mongoose

115

Be MEAN - Node.js

const userSchema = new Schema({


name: require('./fields/fields-name')
, password: String
, email: String
, type: require('./fields/fields-type')
, created_at: { type: Date, default: Date.now }
});

Agora vamos criar os fields faltantes para password , email e created_at ,


voc deve ter percebido que name , password e email so iguais.
Ento para que criar um arquivo para cada se podemos reaproveitar?
Calma que logo logo voc entender essa separao, vamos continuar:
const userSchema = new Schema({
name: require('./fields/field-name')
, password: require('./fields/field-password')
, email: require('./fields/field-email')
, type: require('./fields/field-type')
, created_at: require('./fields/field-created_at')
});

Pronto agora atomizamos nossos fields ento est na hora de trabalhar em cada
campo para definir suas peculiaridades, vamos comear pelo name
( fields/field-name ):
module.exports = { type: String }

Vamos definir para esse Field:


index;
virtual;
getter e setter;
validate.

Mongoose

116

Be MEAN - Node.js

const _get = (v) => v.toUpperCase();


const _set = (v) => v.toLowerCase();
const _validate = (v) => v.length > 3
const Field = {
type: String
, get: _get
, set: _set
, validate: [_validate, 'Nome precisa ser maior que 3 caracteres'
, required: true
, index: true
}
module.exports = Field;

Escrever aqui o que falta


Vamos separar os contextos, perceba que o app.js est com muita
responsabilidade, por exemplo o objeto de User que tende a ser o Controller,
ento vamos refatorar esse cdigo retirando o objeto User de app.js para
um arquivo novo chamado controller.js , contendo o seguinte cdigo:

Mongoose

117

Be MEAN - Node.js

const Model = require('./model');


const Controller = {
create: (req, res) => {
Model.create(req, res);
}
, retrieve: (req, res) => {
Model.retrieve(req, res);
}
, update: () => {
const query = { name: /jean suissa/i };
const mod = {name: 'Itacir Pompeu'};
Model.update(req, res);
}
, delete: () => {
const query = { name: /Itacir Pompeu/i };
Model.delete(req, res);
}
};
module.exports = Controller;

Perceba que mudei o nome do objeto de User para Controller a fim de


deixar o cdigo mais genrico, voc entender o porque mais para frente.

Mongoose

118

Be MEAN - Node.js
Tendo retirado o cdigo anterior de app.js logicamente precisamos importar
esse Controller para o app , ficando assim:
'use strict';
const http = require('http');
const Controller = require('./controller');
http.createServer((req, res) => {
let msg = '';
switch(req.url){
case '/api/users/create':
msg = 'USUARIO CADASTRADO';
Controller.create(req, res);
break;
default:
msg = 'ROTA NAO ENCONTRADA';
break;
}
res.end(msg);
}).listen(3000, () => {
console.log('Servidor rodando em localhost:3000');
});

Agora precisamos refatorar nossa funo de create :


create: () => {
const obj = {
name: 'Jean Suissa'
, password: '1234567'
, email: 'suissera@webschool.io'
};
Model.create(obj);
}

Para receber o objeto a ser inserido, esse objeto vem de onde?

Mongoose

119

Be MEAN - Node.js

Como no!??? Do objeto req !


Ento refatorando o cdigo ficar:
create: (req, res) => {
Model.create(req, res);
}

Se refatorarmos essa funo agora precisamos refatorar a funo create do


Model:

Mongoose

120

Be MEAN - Node.js

create: (req, res) => {


let queryData = '';
req.on('data', (data) => {
queryData += data;
});
req.on('end', () => {
const obj = querystring.parse(queryData);
User.create(obj, (err, data) => {
if (err) return console.log('Erro:', err);
return console.log('Inserido:', data);
});
});
}

Caraio mas como que ficou assim!!???

Se liga s!
Para recebermos dados no nosso servidor ns escutaremos o evento data do
objeto Request( req ), pois pense que voc pode estar enviando um vdeo, logo
no tem como o Node.js ter uma funo para receber o vdeo inteiro, em vez
disso precisamos apenas escutar o evento data at o Request emitir o evento
end .

Sei que no aula sobre http , mas o objeto de Request uma instncia de
http.IncomingMessage. que implementa a interface de Readable Stream e uma
interface de Readable stream uma abstrao para uma fonte de dados que
voc esteja lendo, em outras palavras os dados so lidos em um fluxo legvel,
palavras da prpria documentao. :p
Ou seja, (quase)sempre que voc for ler dados com o Node.js poder utilizar
essa interface para leitura de dados.

Mongoose

121

Be MEAN - Node.js
ps: O Node.js no verifica se o Content-Length e o comprimento do corpo que
tenham sido enviados so iguais ou no.
Vamos voltar para o cdigo, agora que ja o entendemos:
create: (req, res) => {
let queryData = '';
req.on('data', (data) => {
queryData += data;
});
req.on('end', () => {
const obj = querystring.parse(queryData);
User.create(obj, (err, data) => {
if (err) return console.log('Erro:', err);
return console.log('Inserido:', data);
});
});
}

Ento entendemos que, enquanto nosso servidor recebe os dados ele vai
adicionando em queryData para depois esse objeto ser parseado, por
querystring.parse , de string para objeto quando executar o evento end de

Request.
Para depois inserirmos com User.create e PIMBA!

Mongoose

122

Be MEAN - Node.js

Agora que conseguimos chegar no banco precisamos retornar a resposta que


retorna para o usurio, para isso precisamos refatorar o Model, pois ele que
recebe a resposta do Mongoose.
create: (req, res) => {
let queryData = '';
req.on('data', (data) => {
queryData += data;
});
req.on('end', () => {
const obj = querystring.parse(queryData);
User.create(obj, (err, data) => {
console.log('criando');
if (err) return console.log('Erro:', err);
res.writeHead(200, {'Content-Type': 'application/json'});
return res.end(JSON.stringify(data));
});
});
}

Como estamos criando uma API vamos retornar nossa resposta em forma de
JSON, por isso esse trecho:

Mongoose

123

Be MEAN - Node.js

res.writeHead(200, {'Content-Type': 'application/json'});


return res.end(JSON.stringify(data));

Onde res.writeHead(200, {'Content-Type': 'application/json'})


escreve o cabealho da resposta e res.end(JSON.stringify(data)); finaliza
a conexo enviando os dados em formato de string( JSON.stringify ).
A funo res.end finaliza a conexo enviando uma string para o cliente,
podemos utilizar a funo res.write que alm de string tambm aceita buffer,
sendo utf8 sua codificao padro.
Vamos para a prxima funo, retrieve. Para isso precisamos adicionar sua rota
em app.js :
'use strict';
const http = require('http');
const Controller = require('./controller');
http.createServer((req, res)=> {
let msg = '';
switch(req.url){
case '/api/users/create':
Controller.create(req, res);
break;
case '/api/users':
Controller.retrieve(req, res);
break;
default:
msg = 'ROTA NAO ENCONTRADA';
break;
}
}).listen(3000, ()=> {
console.log('Servidor rodando em localhost:3000');
});

Mongoose

124

Be MEAN - Node.js
Agora vamos fazer a listagem dos usurios, ento seguimos o mesmo padro do
Create, tanto em app.js como em controller.js , logo vamos refatorar o
Model.
retrieve: (req, res) => {
const query = {};
User.find(query, (err, data) => {
if (err) return console.log('Erro:', err);
res.writeHead(200, {'Content-Type': 'application/json'});
res.end(JSON.stringify(data));
});
}

No iremos perder mais tempo com isso pois usaremos o Express futuramente.
Ento vamos fazer mais uma funo do CRUD, o Update, para isso iniciamos
adicionando sua rota no app.js :

Mongoose

125

Be MEAN - Node.js

'use strict';
const http = require('http');
const url = require('url');
const Controller = require('./controller');
http.createServer((req, res)=> {
var url_parts = url.parse(req.url);
let msg = '';
switch(url_parts.pathname){
case '/api/users/create':
Controller.create(req, res);
break;
case '/api/users':
Controller.retrieve(req, res);
break;
case '/api/users/update':
Controller.update(req, res);
break;
default:
res.end('ROTA NAO ENCONTRADA');
break;
}
}).listen(3000, ()=> {
console.log('Servidor rodando em localhost:3000');
});

O Controller fica com o padro:


update: (req, res) => {
Model.update(req, res);
}

Porm olha como fica o Model:

Mongoose

126

Be MEAN - Node.js

update: (req, res) => {


let queryData = '';
req.on('data', (data) => {
queryData += data;
});
req.on('end', () => {
const mod = querystring.parse(queryData);
const url_parts = url.parse(req.url);
const query = querystring.parse(url_parts.query);
User.update(query, mod, (err, data) => {
if (err) return console.log('Erro:', err);
res.writeHead(200, {'Content-Type': 'application/json'});
return res.end(JSON.stringify(data));
});
});
}

Utilizamos a mesma forma de pegar os valor da funo create , a nica


diferena como pegamos o valor da query ento vamos analisar:

Mongoose

127

Be MEAN - Node.js

const mod = querystring.parse(queryData);


//name=ValorNOVO
const url_parts = url.parse(req.url);
/*
Url {
protocol: null,
slashes: null,
auth: null,
host: null,
port: null,
hostname: null,
hash: null,
search: '?name=valorBUSCADO',
query: 'name=valorBUSCADO',
pathname: '/api/users/update',
path: '/api/users/update?name=valorBUSCADO',
href: '/api/users/update?name=valorBUSCADO' }
*/
const query = querystring.parse(url_parts.query);
// { name: 'valorBUSCADO' }

Primeiramente parseamos queryData para pegar o contedo do envio, para


depois utilizar url.parse para colocar os dados da url requisitada no objeto
url_parts e depois precisamos apenas pegar o valor do atributo
url_parts.query ( 'name=valorBUSCADO' ), utilizando querystring.parse

convertemos essa string no objeto query e PIMBA!

Depois alteramos com User.update passando os objetos query e mod


como parmetros

Mongoose

128

Be MEAN - Node.js

User.update(query, mod, (err, data) => {


if (err) return console.log('Erro:', err);
console.log('Alterado:', JSON.stringify(data));
res.writeHead(200, {'Content-Type': 'application/json'});
return res.end(JSON.stringify(data));
});

Agora para finalizar o CRUD faremos a funo Delete, iniciando por adicionar sua
rota em app.js :

Mongoose

129

Be MEAN - Node.js

'use strict';
const http = require('http');
const url = require('url');
const Controller = require('./controller');
http.createServer((req, res)=> {
var url_parts = url.parse(req.url);
let msg = '';
switch(url_parts.pathname){
case '/api/users/create':
Controller.create(req, res);
break;
case '/api/users':
Controller.retrieve(req, res);
break;
case '/api/users/update':
Controller.update(req, res);
break;
case '/api/users/delete':
Controller.delete(req, res);
break;
default:
msg = 'ROTA NAO ENCONTRADA';
break;
}
}).listen(3000, () => {
console.log('Servidor rodando em localhost:3000');
});

Percebeu essa parte url_parts = url.parse(req.url) ?


Pois ento, utilizamos ela para separar a query da url para que a requisio
chegue na rota correta, se no a rota api/users/update?name=valorNOVO
nunca chegar em case '/api/users/update .
Refatorando a funo delete me controller.js :

Mongoose

130

Be MEAN - Node.js

const Model = require('./model');


const Controller = {
create: (req, res) => {
Model.create(req, res);
}
, retrieve: (req, res) => {
Model.retrieve(req, res);
}
, update: (req, res) => {
Model.update(req, res);
}
, delete: (req, res) => {
Model.delete(req, res);
}
};
module.exports = Controller;

delete: (req, res) => {


const url_parts = url.parse(req.url);
const query = querystring.parse(url_parts.query);
User.remove(query, (err, data) => {
if (err) return console.log('Erro:', err);
res.writeHead(200, {'Content-Type': 'application/json'});
return res.end(JSON.stringify(data));
});
}

Ahhhhh agora voc entendeu como pegar os valores da requisio na URL,


vamos refatorar a funo Retrieve para que ela aceite valores para buscar.

Mongoose

131

Be MEAN - Node.js

retrieve: (req, res) => {


const url_parts = url.parse(req.url);
const query = querystring.parse(url_parts.query);
User.find(query, (err, data) => {
if (err) return console.log('Erro:', err);
res.writeHead(200, {'Content-Type': 'application/json'});
res.end(JSON.stringify(data));
});
}

Agora para fazer a funo get usando User.findOne ficou bem fcil:
get: (req, res) => {
const url_parts = url.parse(req.url);
const query = querystring.parse(url_parts.query);
User.findOne(query, (err, data) => {
if (err) return console.log('Erro:', err);
res.writeHead(200, {'Content-Type': 'application/json'});
res.end(JSON.stringify(data));
});
}

Ento usaremos a funo retrieve para listagem dos usurios e get para
consultar 1 usurio, no esquea de adicionar a rota em app.js e a funo
controller.js .

Percebeu algum padro nesse CRUD?

Mongoose

132

Be MEAN - Node.js

Vamos ento analisar o cdigo de model.js :


'use strict';
const url = require('url');
const querystring = require('querystring');
const mongoose = require('mongoose');
const Schema = require('./schema');
const User = mongoose.model('User', Schema);
const CRUD = {
create: (req, res) => {
let queryData = '';
req.on('data', (data) => {
queryData += data;
});
req.on('end', () => {
const obj = querystring.parse(queryData);
User.create(obj, (err, data) => {
if (err) return console.log('Erro:', err);
console.log('Inserido:', JSON.stringify(data));
res.writeHead(200, {'Content-Type': 'application/json'});
return res.end(JSON.stringify(data));
});
});
}
, retrieve: (req, res) => {
const url_parts = url.parse(req.url);

Mongoose

133

Be MEAN - Node.js
const query = querystring.parse(url_parts.query);
User.find(query, (err, data) => {
if (err) return console.log('Erro:', err);
res.writeHead(200, {'Content-Type': 'application/json'});
res.end(JSON.stringify(data));
});
}
, get: (req, res) => {
const url_parts = url.parse(req.url);
const query = querystring.parse(url_parts.query);
User.findOne(query, (err, data) => {
if (err) return console.log('Erro:', err);
res.writeHead(200, {'Content-Type': 'application/json'});
res.end(JSON.stringify(data));
});
}
, update: (req, res) => {
let queryData = '';
req.on('data', (data) => {
queryData += data;
});
req.on('end', () => {
const mod = querystring.parse(queryData);
const url_parts = url.parse(req.url);
const query = querystring.parse(url_parts.query);
User.update(query, mod, (err, data) => {
if (err) return console.log('Erro:', err);
res.writeHead(200, {'Content-Type': 'application/json'});
return res.end(JSON.stringify(data));
});
});
}

Mongoose

134

Be MEAN - Node.js
, delete: (req, res) => {
const url_parts = url.parse(req.url);
const query = querystring.parse(url_parts.query);
User.remove(query, (err, data) => {
if (err) return console.log('Erro:', err);
res.writeHead(200, {'Content-Type': 'application/json'});
return res.end(JSON.stringify(data));
});
}
};
module.exports = CRUD;

Perceba que o callback em cada funo o mesmo:


(err, data) => {
if (err) return console.log('Erro:', err);
res.writeHead(200, {'Content-Type': 'application/json'});
return res.end(JSON.stringify(data));
}

Logo podemos encapsular sua lgica em uma funo:


const callback = (err, data) => {
if (err) return console.log('Erro:', err);
res.writeHead(200, {'Content-Type': 'application/json'});
return res.end(JSON.stringify(data));
}

Deixando assim o cdigo de model.js :


'use strict';

Mongoose

135

Be MEAN - Node.js
const url = require('url');
const querystring = require('querystring');
const mongoose = require('mongoose');
const Schema = require('./schema');
const User = mongoose.model('User', Schema);
const callback = (err, data, res) => {
if (err) return console.log('Erro:', err);
res.writeHead(200, {'Content-Type': 'application/json'});
return res.end(JSON.stringify(data));
};
const CRUD = {
create: (req, res) => {
let queryData = '';
req.on('data', (data) => {
queryData += data;
});
req.on('end', () => {
const obj = querystring.parse(queryData);
User.create(obj, (err, data) => callback(err, data, res));
});
}
, retrieve: (req, res) => {
const url_parts = url.parse(req.url);
const query = querystring.parse(url_parts.query);
User.find(query, (err, data) => callback(err, data, res));
}
, get: (req, res) => {
const url_parts = url.parse(req.url);
const query = querystring.parse(url_parts.query);
User.findOne(query, (err, data) => callback(err, data, res));
}
, update: (req, res) => {
let queryData = '';

Mongoose

136

Be MEAN - Node.js
req.on('data', (data) => {
queryData += data;
});
req.on('end', () => {
const mod = querystring.parse(queryData);
const url_parts = url.parse(req.url);
const query = querystring.parse(url_parts.query);
User.update(query, mod, (err, data) => callback(err, data, res));
});
}
, delete: (req, res) => {
const url_parts = url.parse(req.url);
const query = querystring.parse(url_parts.query);
User.remove(query, (err, data) => callback(err, data, res));
}
};
module.exports = CRUD;

Nesse caso no podemos fazer apenas:


User.create(obj, callback);

Pois para isso nosso callback deveria ter os mesmo parmetros e como nele
que estamos devolvendo a resposta com res.end(JSON.stringify(data))
precisamos ento fazer a chamada da funo para passar o Request como ltimo
parmetro:
(err, data) => callback(err, data, res)

Vamos refatorar essa parte:

Mongoose

137

Be MEAN - Node.js

const url_parts = url.parse(req.url);


const query = querystring.parse(url_parts.query);

Pois ela tambm usada em mais de 1 lugar, ficando assim:


const getQuery = (req) => {
return querystring.parse(url.parse(req.url).query);
};

Finalmente nosso cdigo refatorado esse:


'use strict';
const url = require('url');
const querystring = require('querystring');
const mongoose = require('mongoose');
const Schema = require('./schema');
const User = mongoose.model('User', Schema);
const callback = (err, data, res) => {
if (err) return console.log('Erro:', err);
res.writeHead(200, {'Content-Type': 'application/json'});
return res.end(JSON.stringify(data));
};
const getQuery = (req) => {
return querystring.parse(url.parse(req.url).query);
};
const CRUD = {
create: (req, res) => {
let queryData = '';
req.on('data', (data) => {
queryData += data;
});
req.on('end', () => {

Mongoose

138

Be MEAN - Node.js
const obj = querystring.parse(queryData);
User.create(obj, (err, data) => callback(err, data, res));
});
}
, retrieve: (req, res) => {
const query = getQuery(req);
User.find(query, (err, data) => callback(err, data, res));
}
, get: (req, res) => {
const query = getQuery(req);
User.findOne(query, (err, data) => callback(err, data, res));
}
, update: (req, res) => {
let queryData = '';
req.on('data', (data) => {
queryData += data;
});
req.on('end', () => {
const query = getQuery(req);
const mod = querystring.parse(queryData);
User.update(query, mod, (err, data) => callback(err, data, res));
});
}
, delete: (req, res) => {
const query = getQuery(req);
User.remove(query, (err, data) => callback(err, data, res));
}
};
module.exports = CRUD;

Aproveitando o ensejo vamos ajeitar o controller.js para:

Mongoose

139

Be MEAN - Node.js

const Model = require('./model');


const Controller = {
create: Model.create
, retrieve: Model.retrieve
, get: Model.get
, update: Model.update
, delete: Model.delete
};
module.exports = Controller;

Mais refatorao
Dessa vez iremos refatorar algo muito importante em uma API REST, a
aceitao de diferentes verbos do HTTP na mesma rota, ento vamos comear
refatorando a rota /api/users para receber os verbos:
GET
POST
PUT
DELETE

Ento veja como ficou o swicth das rotas em app.js :

Mongoose

140

Be MEAN - Node.js

var url_parts = url.parse(req.url);


switch (url_parts.pathname) {
case '/api/users':
switch (req.method.toLowerCase()) {
case 'get':
Controller.retrieve(req, res);
break;
case 'post':
Controller.create(req, res);
break;
case 'put':
Controller.update(req, res);
break;
case 'delete':
Controller.delete(req, res);
break;
}
break;
case '/api/users/get':
Controller.get(req, res);
break;
default:
res.end('ROTA NAO ENCONTRADA');
break;
}

Notou de onde vem o verbo?


No Request h o atributo method que nos fornece essa informao, depois
bastou criar um switch para testar qual o verbo e chamar sua funo correta.
Para finalizar essa refatorao vamos mudar o nome das funes:
retrieve para find
get para findOne
delete para remove

Em app.js :

Mongoose

141

Be MEAN - Node.js

'use strict';
const http = require('http');
const url = require('url');
const Controller = require('./controller-teste');
http.createServer((req, res) => {
var url_parts = url.parse(req.url);
switch (url_parts.pathname) {
case '/api/users':
switch (req.method.toLowerCase()) {
case 'get':
Controller.find(req, res);
break;
case 'post':
Controller.create(req, res);
break;
case 'put':
Controller.update(req, res);
break;
case 'delete':
Controller.remove(req, res);
break;
}
break;
case '/api/users/get':
Controller.findOne(req, res);
break;
default:
res.end('ROTA NAO ENCONTRADA');
break;
}
}).listen(3000, () => {
console.log('Servidor rodando em localhost:3000');
});

Em controller.js :

Mongoose

142

Be MEAN - Node.js

const Model = require('./model-teste');


const Controller = {
create: Model.create
, find: Model.find
, findOne: Model.findOne
, update: Model.update
, remove: Model.remove
};
module.exports = Controller;

E para finalizar em model :


'use strict';
const url = require('url');
const querystring = require('querystring');
const mongoose = require('mongoose');
const Schema = require('./schema-teste');
const User = mongoose.model('User', Schema);
const callback = (err, data, res) => {
if (err) return console.log('Erro:', err);
res.writeHead(200, {'Content-Type': 'application/json'});
return res.end(JSON.stringify(data));
};
const getQuery = (req) => {
return querystring.parse(url.parse(req.url).query);
};
const create = (req, res) => {
let queryData = '';
req.on('data', (data) => {
queryData += data;
});
req.on('end', () => {

Mongoose

143

Be MEAN - Node.js
const obj = querystring.parse(queryData);
User.create(obj, (err, data) => callback(err, data, res));
});
};
const find = (req, res) => {
const query = getQuery(req);
User.find(query, (err, data) => callback(err, data, res));
};
const findOne = (req, res) => {
const query = getQuery(req);
User.findOne(query, (err, data) => callback(err, data, res));
};
const update = (req, res) => {
let queryData = '';
req.on('data', (data) => {
queryData += data;
});
req.on('end', () => {
const query = getQuery(req);
const mod = querystring.parse(queryData);
User.update(query, mod, (err, data) => callback(err, data, res));
});
};
const remove = (req, res) => {
const query = getQuery(req);
User.remove(query, (err, data) => callback(err, data, res));
};
const CRUD = {
create

Mongoose

144

Be MEAN - Node.js
, find
, findOne
, update
, remove
};
module.exports = CRUD;

Atomic Design
Essa estrutura que eu utilizo baseada no Atomic Design que utilizo no front-end,
porm eu modifiquei um pouco essa metodologia para adicionar a parte de
Comportamento para que eu pudesse extender ela com novas funcionalidades.
Essa palestra est gravada aqui na InfoQ.
Nesse caso cada tomo possuir um comportamento padro que pode ser
sobrescrito quando adicionado em uma molcula, tambm podendo mudar
quando adicionado em um organismo.
Ento vamos entender quais so suas partes.

tomo
O tomo a menor parte indivisvel do Mongoose.
Sabe qual ?

Mongoose

145

Be MEAN - Node.js

Como visto anteriormente a parte indivisvel da nossa arquitetura o Field o qual


possui seus atributos, os quais podem ser quarks.
Vamos analisar o Field name :
const _get = (v) => v.toUpperCase();
const _set = (v) => v.toLowerCase();
const _validate = (v) => v.length > 3;
const Field = {
type: String
, get: _get
, set: _set
, validate: [_validate, 'Nome precisa ser maior que 3 caracteres'
, required: true
, index: true
}
module.exports = Field;

Quarks

Mongoose

146

Be MEAN - Node.js
Levando isso em considerao podemos dizer que as partes que formam nosso
tomo so os quarks:
type
get
set
validate
required
index
Vamos refatorar o cdigo, para reorganizar e refatorar o validate para objeto:
// quarks
const quark_get = (v) => v.toUpperCase();
const quark_set = (v) => v.toLowerCase();
const quark_validate = {
validator: (v) => v >= 3
, message: 'Nome {VALUE} precisa ser maior que 3 caracteres'
};
const Atom = {
type: String
, get: quark_get
, set: quark_set
, validate: quark_validate
, required: true
, index: true
}
module.exports = Atom;

Vamos separar em arquivos os quarks que so funes ou objetos, pois podemos


reaproveit-las futuramente:
// quark-toUpper.js
module.exports = (v) => v.toUpperCase();

Mongoose

147

Be MEAN - Node.js

// quark-toLower.js
module.exports = (v) => v.toLowerCase();

// quark-validate-string-lengthGTE3
module.exports = {
validator: (v) => v >= 18
, message: 'Nome {VALUE} precisa ser maior que 3 caracteres'
};

Com isso o arquivo do tomo ficou assim:


const Atom = {
type: String
, get: require('./../quarks/quark-toUpper')
, set: require('./../quarks/quark-toLower')
, validate: require('./../quarks/quark-validate-string-lengthGTE3')
, required: true
, index: true
}
module.exports = Atom;

Molcula
Sabendo que o Field o tomo logicamente a Molcula ser o Schema, ento
vamos utilizar o seguinte Schema:
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const Molecule = {
name: require('./fields/field-name')
}
module.exports = new Schema(Molecule);

Mongoose

148

Be MEAN - Node.js

Organismo
Para finalizar esse conceito o o Organismo ser o Model:
const url = require('url');
const querystring = require('querystring');
const Schema = require('./schema');
const User = mongoose.model('User', Schema);
const callback = (err, data, res) => {
if (err) return console.log('Erro:', err);
res.writeHead(200, {'Content-Type': 'application/json'});
return res.end(JSON.stringify(data));
};
const getQuery = (_url) => {
const url_parts = url.parse(_url);
return querystring.parse(url_parts.query);
};
const create = (req, res) => {
let queryData = '';
req.on('data', (data) => {
queryData += data;
});
req.on('end', () => {
const obj = querystring.parse(queryData);
User.create(obj, (err, data) => callback(err, data, res));
});
};
const find = (req, res) => {
const query = getQuery(req.url);
User.find(query, (err, data) => callback(err, data, res));
};
const findOne = (req, res) => {

Mongoose

149

Be MEAN - Node.js
const query = getQuery(req.url);
User.findOne(query, (err, data) => callback(err, data, res));
};
const update = (req, res) => {
let queryData = '';
req.on('data', (data) => {
queryData += data;
});
req.on('end', () => {
const mod = querystring.parse(queryData);
const query = getQuery(req.url);
User.update(query, mod, (err, data) => callback(err, data, res));
});
};
const remove = (req, res) => {
const query = getQuery(req.url);
User.remove(query, (err, data) => callback(err, data, res));
};
const CRUD = {
create
, find
, findOne
, update
, remove
};
module.exports = CRUD;

Como o Organismo possui seu prprio comportamento(Behavior), podemos


separar suas funes desse arquivo ficando:

Mongoose

150

Be MEAN - Node.js

'use strict';
const mongoose = require('mongoose');
const url = require('url');
const querystring = require('querystring');
const Schema = require('./schema');
const User = mongoose.model('User', Schema);
const callback = (err, data, res) => {
if (err) return console.log('Erro:', err);
res.writeHead(200, {'Content-Type': 'application/json'});
return res.end(JSON.stringify(data));
};
const getQuery = (_url) => {
const url_parts = url.parse(_url);
return querystring.parse(url_parts.query);
};
const create = require('./actions/action-create');
const find = require('./actions/action-find');
const findOne = require('./actions/action-findOne');
const update = require('./actions/action-update');
const remove = require('./actions/action-remove');
const CRUD = {
create
, find
, findOne
, update
, remove
};
module.exports = CRUD;

Porm perceba que as Actions necessitam do callback e do getQuery , por


isso vamos separ-los tambm:

Mongoose

151

Be MEAN - Node.js

// action-get-query-http.js
module.exports = (_url) => {
return require('querystring').parse(require('url').parse(_url).query);
};

// action-response-200-json.js
module.exports = (err, data, res) => {
if (err) return console.log('Erro:', err);
res.writeHead(200, {'Content-Type': 'application/json'});
return res.end(JSON.stringify(data));
};

Agora as Actions do Field ficam assim:


// action-create.js
const callback = require('./action-response-200-json');
module.exports = (Model) => {
return (req, res) => {
let queryData = '';
req.on('data', (data) => {
queryData += data;
});
req.on('end', () => {
const obj = require('querystring').parse(queryData);
Model.create(obj, (err, data) => callback(err, data, res));
});
};
};

Mongoose

152

Be MEAN - Node.js

// action-find.js
const callback = require('./action-response-200-json');
const getQuery = require('./action-get-query-http');
module.exports = (Model) => {
return (req, res) => {
const query = getQuery(req.url);
Model.find(query, (err, data) => callback(err, data, res));
};
};

// action-findOne
const callback = require('./action-response-200-json');
const getQuery = require('./action-get-query-http');
module.exports = (Model) => {
return (req, res) => {
const query = getQuery(req.url);
Model.findOne(query, (err, data) => callback(err, data, res));
};
};

Mongoose

153

Be MEAN - Node.js

// action-update
const callback = require('./action-response-200-json');
const getQuery = require('./action-get-query-http');
module.exports = (Model) => {
return (req, res) => {
let queryData = '';
req.on('data', (data) => {
queryData += data;
});
req.on('end', () => {
const mod = require('querystring').parse(queryData);
const query = getQuery(req.url);
Model.update(query, mod, (err, data) => callback(err, data, res));
});
};
};

// action-remove
const callback = require('./action-response-200-json');
const getQuery = require('./action-get-query-http');
module.exports = (Model) => {
return (req, res) => {
const query = getQuery(req.url);
User.remove(query, (err, data) => callback(err, data, res));
};
};

Logo atomizamos as 4 funes do CRUD para que possa ser reaproveitado em


todos nossos futuros sistemas.
Agora o Organismo ficou assim:

Mongoose

154

Be MEAN - Node.js

require('./db/config');
const mongoose = require('mongoose');
const Schema = require('./schema');
const Model = mongoose.model('User', Schema);
// Precisa passar o Model para as aes
const create = require('./actions/action-create')(Model);
const find = require('./actions/action-find')(Model);
const findOne = require('./actions/action-findOne')(Model);
const update = require('./actions/action-update')(Model);
const remove = require('./actions/action-remove')(Model);
const CRUD = {
create
, find
, findOne
, update
, remove
};
module.exports = CRUD;

Muito melhor no?

UM ADENDO MUITO IMPORTANTE!!!


Quando estava trabalhando com TDD nesse modelo senti falta de exportar o
Model no Organism para facilitar nossa vida nos testes.
Por exemplo nesse teste:

Mongoose

155

Be MEAN - Node.js

'use strict';
const Doador = require('./../Organisms/doadorOrganism');
const expect = require('chai').expect;
describe('Setter Schema doadorSchema', () => {
// body...
describe('setter to uppercase', () => {
// body...
it('primeiro nome ONLY lower case save in mongo', () => {
const d = new Doador.Organism();
const fieldTestName = 'primeiroNome';
const fieldTestValue = 'ERNI';
d[fieldTestName] = fieldTestValue;
d.save((Doador) => {
expect(Doador[fieldTestName]).to.be.equal('erni');
});
});
});
});

Mongoose

156

Be MEAN - Node.js

Events
Muitos objetos do Node.js so capazes de emitir eventos, ou seja, eles so
Eventemitters. Um exemplo o net.Server, ele pode emitir eventos todas as
vezes em que recebe uma nova requisio ou quando termina o processo de
uma, o fs.Stream emite eventos toda vez que trabalha com um arquivo, todos
objetos que emitem eventos no Node.js so instncias de events.EventEmitter,
esse mdulo pode ser acessado atravs do mdulo require(events).
Traduo Livre from
(https://nodejs.org/docs/latest/api/events.html#events_events).
Eventos podem ser nominados de acordo com a necessidade de cada funo,
contudo eles seguem um padro para emisso (emit) e escuta (listener) de
eventos, quanto a transmiso de eventos chama-se a funo
obj.emit(event:name, action), para ouvir um evento chama-se a funo
obj.on(event:name, action). Esse padro se repete em vrios locais do mundo
javascript, inclusive em frameworks de frontend, como o angularJS.
Vamos ver o primeiro exemplo mais bsico de um EventEmiter, onde o primeiro
passo ser criar um arquivo.js e requerir o mdulo events. O Cdigo 01 um
mdulo simples que escuta dois eventos, o primeiro evento registrado o
time:event e o segundo mod:three.

Cdigo 01

Eventos

157

Be MEAN - Node.js

'use strict';
const events = require('events');
const em = new events.EventEmitter();
em.on("time:event", timeEvent);
em.on("mod:three", mod3Event);
function timeEvent(interval) {
console.log('timeEvent '+interval);
}
function mod3Event(mod3) {
console.log('3 mod %s === 0 ',mod3);
}
module.exports = em;

Os eventos criados no cdigo 01, seguem um padro simples, onde um nome


criado para o evento, e em seguida uma funo de callback executada,
transmitindo um poder muito grande ao cdigo do Node.js. Existem maneiras
mais interessantes para se usar eventos, e com o tempo fica claro como vrios
mdulos do npm usam a classe events para melhorar seus feedbacks.
Pois bem, o cdigo 02 onde o mdulo de eventos ser usado para fechar essa
ideia de events emitter, mostrando uma forma simples de como ele funciona.

Cdigo 02

Eventos

158

Be MEAN - Node.js

'use strict';
const em = require('./events');
setInterval(( function() {
let i = 1;
return function () {
if(i % 3 === 0) {
em.emit('mod:three',i++);
} else {
em.emit('time:event', i++);
}
};
})(),1000);

Esse cdigo muito simples, feito apenas para fins demonstrativos, sobre a
criao bsica de um EventEmitter. O prximo exemplo um caso de uso mais
efetivo, onde ser extendido o comportamento do EventEmiter para um mdulo
criado pelo desenvolvedor, ou seja, assim como o prprio Node.js usa essa boa
prtica de emitir eventos em vrias classes internas, ns desenvolvedores
podemos extender esse comportamento, atravs de herana.
Ser usado o mdulo require(util), presente no core do Node.js, para nos ajudar
a fazer a herana, e transformar um simples mdulo chamado User, em um
EventEmiter. O cdigo 03 d incio ao mdulo.

Cdigo 03

Eventos

159

Be MEAN - Node.js

'use strict';
const EventEmitter = require('events').EventEmitter;
const util = require('util');
function User () {
EventEmitter.call(this);
}
util.inherits(User, EventEmitter);
module.exports = User;

Ok, essa uma forma bsica para criar um mdulo, que pode emitir e escutar
eventos, pois bem agora esse mdulo ter um comportamento simples, que
receber dados de um usurio, e ao chamar a sua funo save(), ele emitir um
evento(save:user, sendEmail) caso tudo corra bem, esse evento chamar em
seu callback uma funo que simula o envio de um email, caso contrrio emitir
um erro(error,err), que em seu callback chamar uma funo que levantar um
erro. Vejamos agora o cdigo 04.

Cdigo 04
use strict;
const EventEmitter = require('events').EventEmitter;
const util = require('util');
function User (data) {
this.data = data;
this.on('user:save', sendMail);
this.on('error', sendError);
EventEmitter.call(this);
}

User.prototype.save = function () {

Eventos

160

Be MEAN - Node.js
if(this.data.name){
this.emit('user:save',this.data);
}
else {
this.emit('error', new TypeError('User need an name'));
}
};
util.inherits(User, EventEmitter);
function sendMail(user) {
user.pass = Math.floor(Math.random() * (1000000 - 900000)) + 900000
util.log(`\n
\tOla ${user.name}!
\tbem vindo seu pass ${user.pass}
\tvoc tem 24 horas para muda-lo
\tou tera que pedir reenvio\n`
);
}
function sendError(err) {
throw err;
}
module.exports = User;

O mdulo User, possui algumas particularidades que devem ser explicadas, como
o uso do mdulo util, com ele pode-se fazer muitas coisas legais como herana,
inspeo de objetos, logs entre outros. Vale a pena verificar sua documentao
em: https://nodejs.org/api/util.html
Essa foi uma breve intruduo a eventos no nodeJS. Agora iremos aprender a
usar eventos nos models do mongoose, pois todo model do mongoose um
evento Emitter, ele possui alguns eventos padres, muito teis para melhorar a
arquitetura de um sistema.
Conhecendo o evento pre e post do mongoose, veja um model no cdigo 05.
Esse model pode executar tarefas antes e/ou quando for executar alguma funo,
como: save, create, find ou ou qualquer funo interna.
Eventos

161

Be MEAN - Node.js

Cdigo 05
'use strict';
const mongoose = require('mongoose');
const util = require('util');
function pokemonHandler () {
let Schema = mongoose.Schema;
const ObjectId = Schema.ObjectId;
const schema = new Schema({
id : ObjectId,
name : {type : String, trin : true},
type : {type : String, trin : true},
attack : {type : Number},
defence : {type : Number},
height : {type : Number},
description : {type : String, trin : true}
});
schema.pre('find',function (next) {
this.start = Date.now();
util.log("finding ...");
next();
});
schema.post('find', function(result) {
setTimeout(function(){
console.log('finding end :P')
},1000);
});
return mongoose.model('Pokemon', schema);
}
module.exports = exports = pokemonHandler();

Primeiramente, veremos como funciona o evento pre. Basicamente ele um


middleware, que chamado antes da sua funo ser chamada. No exemplo,
entretanto, o argumento next() deve ser sempre chamado para que sua funo

Eventos

162

Be MEAN - Node.js
seja executada como o esperado, ou seja, o pre KKKK ser chamado antes do
save, ok?
O evento post muito sugestivo, pois possui exatamente o comportamento de
execuo posterior, na sua chamada no existe necessidade de usar next()
lembre-se disso. No exemplo do model no cdigo 05, ele ser executado 1
segundo aps a execuo da funo find, ou seja, essas duas funes pre e
post so muito teis e devem ser usadas sempre que necessrio.

Eventos

163

Be MEAN - Node.js

Promise no Node.js
O que ? Promise??? uma abstrao para trabalhar com cdigo assncrono de
forma elegante, organizada e simplificada. Existem pessoas crentes que o
Node.js perdeu a chance de usar toda sua api baseada em Promises, contudo
por questes de performance, o callback foi mantido e no apenas isso, outras
abstraes podem ser usadas sobre os callbacks. Para saber do que se trata a
fundo uma Promise, leia a proposta com base de uma expecificao nesse link:
Promise/A+ https://promisesaplus.com/. Nele so abordados detalhes no
cobertos nessa apostila.
O Callback um grande problema no quesito de manuteno de cdigo, e eu
particularmente no gosto de mostrar para meus amigos cdigos com callbacks,
ainda mais se tiver muitos, com Promise no tenho essa preocupao.
Existe uma abstrao para trabalhar com cdigo assncrono em C++ e Java, que
a future, quem conhece essa abstrao entende Promise rapidamente.
Uma Promise composta por trs estados bsicos: pendente: quando ainda est
executando. realizada / fulfilled: quando ela termina e tem um resultado de
sucesso. rejeitada / reject: quando termina e tem algum erro;
O NodeJS desde a verso 0.11.x j possuia a possibilidade de trabalhar com
Promise, usando flag, contudo na verso 0.12.7 ela foi adicionada nativamente,
passando a ser usada diretamente e melhor ainda, como um objeto global na
plataforma, o global.Promise.
Vamos ver um exemplo? Primeiramente, vamos ver como ler um arquivo JSON
usando o mdulo de FS, com a funo readFile sem uso de Promise, no cdigo
01.
Cdigo 01

Promises

164

Be MEAN - Node.js

'use strict';
const fs = require('fs');
fs.readFile('./persons.json','utf-8',function(err, file){
if(!err) console.log(file);
});

Essa a forma clssica de ler um arquivo de modo no bloqueante no Node.js,


aparentemente isso algo muito simples, porm caso tenha dois arquivos ou
mais com valores diferentes e havendo necessidade de manipulao deles, como
no cdigo 02, ficaria bem estranho, ento comea ficar confuso, e cdigo confuso
vira problema. Verifique-o e tente pensar: e se fossem 4 ou 5 arquivos? Confuso
vem a calhar para nomear essa ideia.

Promises

165

Be MEAN - Node.js

'use strict';
const fs = require('fs');
//lendo primeiro arquivo
fs.readFile('./persons.json','utf-8', function(err, persons){
//array para juntar todos
let todos = [];
if(!err){
fs.readFile('./friends.json','utf-8', function(err, friends
if(!err){
//juntando arquivos
todos.push(JSON.parse(persons));
todos.push(JSON.parse(friends));
}
//fazendo uma operao com eles
sendFiles(todos);
});
}
});
function sendFiles(files){
//mapeando os arquivos
var arr = files.map(function(person) {
return person.concat(person)
});
//lendo o resultado
console.log(arr[0]);
}

Isso um problema, eu tive que comentar um cdigo muito pequeno, no que


seja ruim comentar, mas o fato foi que ele ficou pouco intuitivo, muita gente usaria
readFileSync, que seria pior, pois travaria a Tread da aplicao, ou at mesmo
usariam createReadbleStream, o que seria uma opo melhor para a leitura do
cdigo, contudo se pensarmos em reuso, poderamos facilmente usar a Promise
para abstrair esse trabalho e deix-lo simples de ler, testar e manter. Lembremse, testar muito importante. No cdigo 03 faremos uso de Promise, como opo

Promises

166

Be MEAN - Node.js
ao callback hell, vamos criar uma funo genrica que recebe um
PATH/caminho de um arquivo e de forma assncrona nos retorna uma Promise, o
cdigo 03 faz exatamente isso.
Cdigo 03
'use strict';
const fs = require('fs');
function readFile (path) {
return new Promise(function(resolve, reject) {
fs.readFile(path,'utf8',function(err, res) {
err ? reject(err) : resolve(res);
});
});
}
module.exports = readFile;

Recordando, o arquivo fs-promise.js um modulo, ele exporta a funo


readFile(path), essa funo recebe um caminho e nos retorna uma Promise, essa
Promise tem os 3 estados citados anteriormente, PENDING, RESOLVE e
REJECT, ou seja, quando passar um caminho vlido, ser chamado o
resolve(res), se algo errado acontecer ser chamado o reject(err), essa funo
ficou muito simples para reuso, verificando como fica o seu uso no cdigo 04.
Cdigo 04

Promises

167

Be MEAN - Node.js

'use strict';
const readFile = require('./fs-promise');
readFile('./persons.json')
.then(function(data) {
success(data);
})
.catch(function(err){
error(err);
});
readFile('./perso.json')
.then(success , error);
function success (data) {
console.log(data);
}
function error (err) {
console.error(err);
}

Percebam que na primeira chamada eu passei um arquivo com path correto,


onde tudo deu certo e usei then e catch para representar os estados success
e error, e no segundo caso passei o nome errado para representar o estado de
reject/error e no usei o catch, pois o prprio then pode tratar os dois
estados. Vamos entender melhor o que foi feito: minha funo readFile recebeu
um caminho correto, executou o then(callback) e em seu callback vai ler o que
tem no json, e no segundo caso me retornou um erro, ou seja, o cdigo 04
demonstra claramente o comportamento de um Resolve e um Reject sendo
chamado, mas ento agora vamos juntar os arquivos? Lembrando que funes
em javascript podem ser colocadas arrays, isso vai ajudar no uso do Promise.all(
[ ]), que recebe um array de Promises. Veja o cdigo 05.
Cdigo 05

Promises

168

Be MEAN - Node.js

Promise.all([
readFile('./persons.json'),
readFile('./friends.json')
])
.then(function(result) {
console.log(result);
})
.catch(function(err){
console.log(err);
});

Para melhorar o uso Promise.all([]) no repositrio, eu deixei um mdulo chamado


promise-all.js, que ser usado para fazer um exerccio.

Promise Mongoose
Nos models do mongoose assim como temos Eventemitters por padro, podemos
tambm trabalhar com Promise, que ajuda muito a deixar o cdigo simples de
manter e testar. Vamos usar como base o model de pokemons criando na parte
de events, para entendermos como funciona. Em caso de dvida, a
documentao do mongoose possui exemplos bsicos de como usar Promise ao
invs de callbacks. http://mongoosejs.com/docs/promises.html
Cdigo 01

Promises

169

Be MEAN - Node.js

'use strict';
const mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/yourdb');
const Pokemon = require('./models/pokemon');

const pokemon = {
name : "Pompeu Limp",
type : "Fire",
attack : 81,
defence : 65,
height : 1.82,
description : "jiujitero"
};

Pokemon.create(pokemon).then(success , error);
function success(data) {
console.log(data);
}
function error (err) {
console.log(err);
}

Esse o exemplo padro para criar um novo pokemon em uma collection no


mongoose, usando Promise. Vamos ver como simples usar find, findOne,
update e delete, que so muito usados nas operaes de aplicaes web de uma
API Rest. Geralmente voc faz Create, Retrieve, Update e Delete, denominado
de CRUD, j imaginou? Fazer isso usando Promise? Sem callbacks? Ou vrios
if e else aninhados? O create j foi feito, ento vamos aos retrieves nos
cdigos 02 e 03.
Cdigo 02

Promises

170

Be MEAN - Node.js

'use strict';
const mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/pompeuapi');
const Pokemon = require('./models/pokemon');
let promise = Pokemon.find({}).exec();
promise.then(success, error);

Cdigo 03
'use strict';
'
```js
const mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/pompeuapi');
const Pokemon = require('./models/pokemon');
let promise = Pokemon.findOne({ _id : '5666fd32ff4ea39e23e1528f' }).exec();
promise.then(success , error);

Agora que j sabemos como fazer find e findOne, percebam que nesses casos
foi chamada a funo exec() no final, para transformar minha busca em Promise,
que a nica diferena do create, agora verificaremos o update e delete nos
cdigos 04 e 05.
Cdigo 04

Promises

171

Be MEAN - Node.js

const pokemon = {
name : "Pompeu Limp",
type : "Fire",
attack : 99,
defence : 99,
height : 1.82,
description : "jiujitero"
};
let promise = Pokemon
.update({ _id : '5666ff2a9fa2a10c25d57ef7'},pokemon).exec();
promise.then(success , error);

Cdigo 05

let promise = Pokemon.remove({_id : '5666ff2a9fa2a10c25d57ef7'})


promise.then(success , error);

Perceba que o padro simples e segue o mesmo ritmo, quando formos


trabalhar com Express mais adiante, isso vai deixar o cdigo mais claro, fazendo
com que o fluxo da aplicao seja muito intuitivo.
Para finalizar segue uma reflexo sobre cdigo limpo.
Segundo Bjorne, criador do C++, Gosto do meu cdigo elegante e eficiente. A
lgica deve ser direta para dificultar o encobrimento de bugs, as dependncias
devem ser minimas para facilitar a manuteno, o tratamento de erro deve ser
completo de acordo com uma estratgia clara e o desempenho prximo do mais
eficiente de modo a no incitar as pessoas a tornarem o cdigo confuso com
otimizaes sorrateiras. O cdigo limpo faz bem as coisas
Uma reflexo minha: Promise deixa o cdigo limpo, as verses que seguem do
Javascript a Promise est se tornando cdigo de infraestrutura, dando
possibilidade de novas apis serem usadas como fetch e async, que usam

Promises

172

Be MEAN - Node.js
Promise internamente. Em um futuro prximo (2016) o trabalho com cdigo
assncrono ser ridiculamente simples com a implementao da especificao do
novo ecma 2016.

Promises

173

Vous aimerez peut-être aussi