Node Sandbox
Dia 10/08 eu estava palestrando em uma software house de Porto Alegre do porquê de node. E se você ainda tem essa dúvida recomento ver o post do Luis de Por que Stanford trocou Java por JavaScript?.
Acabei criando vários pequenos exemplos para mostrar benefícios, libs, cases. O resultado está disponível no github. Vários tem docker-compose (para Redis, Mongo, RabbitMQ) e scripts no package.json
.
Compartilho aqui também com um pouco mais de detalhamento.
Exemplos básicos
1) Um exemplo bem básico de server sem usar libs:
const http = require('http'),
hostname = '127.0.0.1',
port = 3000
const server = http.createServer((req,res) => {
res.statusCode = 200
res.setHeader('Content-Type','text/plain')
res.end("Hello world\n")
})
server.listen(port, hostname, () => {
console.log(`Server running at http://${hostname}:${port}/`)
})
2) Brincando com o Repl
Crie um arquivo sample.js
com o conteúdo:
function hello(msg){
console.log("Número de argumentos: ", arguments.length)
return `Hello ${msg}`
}
module.exports = {
hello
}
Agora na terminal, execute node
. Com isso você pode iteragir com o código. Exemplo:
var x = require("./sample")
x.hello("adamatti")
Tente chamar a função hello
com mais argumentos ou sem nenhum. Talvez por isso o pessoal ache que javascript é meio zuado :-)
3) Cache de função
const memoize = require("memoize")
function factorial(n,cb){
console.log("chamou a função real: ",n)
if (n==0 || n==1){
return cb(null,1)
}
factorial(n-1,(err,result) => {
cb(null, result * n )
})
}
const arr = [1,2,1,2,1,2,1,2]
const f = memoize(factorial)
for (var i=0; i<arr.length ; i++){
f(arr[i], (err,result) => {
console.log("fatorial - ",arr[i],": ",result)
})
}
Atenção especial para a linha const f = memoize(factorial)
. A função factorial
é passada por parametro para a função memoize
, retornando uma nova função que faz cache das chamadas. Dá para plugar isso com Redis também.
4) Acesso a banco - Mongo
Exemplo simples com listagem e inserção.
//REFERENCES https://github.com/Automattic/monk
const db = require('monk')('localhost/mydb')
const users = db.get('users')
users.find({}).then(data => {
console.log("List of users: ")
for(var i = 0; i < data.length; i++){
console.log("User ",data[i])
}
})
users.insert({name:"Marcelo"})
5) Acesso a banco - Redis
A cada vez que o código é executado o contador é incrementado.
//REFERENCES: https://github.com/mjackson/then-redis
const createClient = require('then-redis').createClient,
db = createClient('tcp://localhost:6379')
db.get("count").then( value => {
console.log ("Current count is ",value)
value = value || 0;
db.set("count",parseInt(value) + 1)
})
6) Exemplo com Promise e Bluebird
const logger = require("log4js").getLogger(),
Promise = require("bluebird")
logger.level = 'debug'
function factorial(n){
if (n==0 || n==1){
return Promise.resolve(1);
}
return factorial(n-1).then( result => result * n)
}
logger.debug("start")
const range = Array.from({length: 1000}, (_, i) => i)
return Promise.map(range, i => {
return factorial(i).then( result => {
logger.trace(i + " => " + result)
})
}).then( () => {
logger.debug("all done")
}).catch(err => {
logger.error("Error: ",err)
})
7) Exemplo de programação funcional com ramdajs.com[Ramda]
Vale conferir o artigo de porquê do Ramda
const R = require("ramda")
var incomplete = R.filter(R.where({complete: false}))
var sortByDate = R.sortBy(R.prop('dueDate'))
var sortByDateDescend = R.compose(R.reverse, sortByDate)
var importantFields = R.project(['title', 'dueDate'])
var groupByUser = R.partition(R.prop('username'))
var activeByUser = R.compose(groupByUser, incomplete)
var topDataAllUsers = R.compose(R.mapObj(R.compose(importantFields, R.take(5), sortByDateDescend)), activeByUser)
var tasks = [
{
username: 'Scott',
title: 'Add `mapObj`',
dueDate: '2014-06-09',
complete: false,
effort: 'low',
priority: 'medium'
},
{
username: 'Michael',
title: 'Finish algebraic types',
dueDate: '2014-06-15',
complete: true,
effort: 'high',
priority: 'high'
}
]
var results = incomplete(tasks)
console.log(results)
Exemplos web
8) Express
Sim, porque todo artigo de node tem que ter express :-)
//REFERENCE
// http://expressjs.com/pt-br/starter/hello-world.html
// https://www.npmjs.com/package/log4js
const express = require('express'),
app = express(),
logger = require('log4js').getLogger()
logger.level = 'trace'
app.get('/', (req, res) => {
logger.trace('Home called')
res.send('Hello World!')
})
app.listen(3001, () => {
logger.info('Example app listening on port 3001!')
})
Express foi uma das primeiras libs para node, mas ele não suporta promises e outras coisas novas.
9) Template Engine com Ejs
Ejs: Para quem está acostumado com o Spring MVC.
const express = require('express'),
app = express()
app.set('view engine', 'ejs')
app.get('/', function(req, res) {
res.render('index',{name: "Adamatti"})
});
app.listen(8080)
console.log('Started - 8080')
10) Koa
//REFERENCES
// http://koajs.com/#introduction
const Koa = require('koa');
const app = new Koa();
app.use(async ctx => {
ctx.body = 'Hello World'
})
app.listen(3000)
11) Hapi
'use strict';
const Hapi = require('hapi'),
server = new Hapi.Server()
server.connection({ port: 3002, host: 'localhost' })
server.route({
method: 'GET',
path: '/',
handler: (request, reply) => {
reply('Hello!')
}
})
server.route({
method: 'GET',
path: '/{name}',
handler: (request, reply) => {
reply('Hello, ' + encodeURIComponent(request.params.name) + '!')
}
})
server.start(err => {
if (err) {
throw err;
}
console.log(`Server running at: ${server.info.uri}`)
})
Frameworks de teste
12) Mocha
const assert = require('assert')
describe('Array', () => {
describe('#indexOf()', () => {
it('should return -1 when the value is not present', () => {
assert.equal(-1, [1,2,3].indexOf(4));
})
})
})
13) Frisby
Para testes de API:
const app = require("../index"),
frisby = require('frisby')
frisby.create('List orders')
.get('http://localhost:3000/orders')
.expectStatus(200)
.expectHeaderContains('content-type', 'application/json')
.expectJSONTypes(0,{
"id": String
})
.toss()
14) Cucumber
Para testes com BDD. Os requisitos são descritos com arquivos .feature
:
Feature: Order Processing
Scenario Outline: Submit an order
Given an order - id: <id>
When I submit it - region: <region>
Then I receive a response - msg: <msg>
Examples:
| id | region | msg |
| submit | US | Order is ok |
| cancel | US | Unable to cancel |
…e o código node para responder isso fica:
const expect = require('expect.js')
module.exports = function() {
this.Given(/^an order \- id: (.*)$/, function (id) {
//TODO implement
})
this.When(/^I submit it \- region: (.*)$/, function (region) {
//TODO implement
})
this.Then(/^I receive a response \- msg: (.*)$/, function (msg) {
//TODO implement
expect(true).to.eql(true)
})
}
Exemplos avançados
15) Cluster
Para iniciar processos node em multiplos processadores.
//REFERENCES https://www.npmjs.com/package/cluster
const cluster = require('cluster/'),
http = require('http')
function app(){
http.createServer(function(req, res){
console.log('%s %s', req.method, req.url)
var body = 'Hello World'
res.writeHead(200, { 'Content-Length': body.length })
res.end(body)
})
}
cluster(app)
.use(cluster.logger('logs'))
.use(cluster.stats())
.use(cluster.pidfiles('pids'))
.use(cluster.cli())
.use(cluster.repl(8888))
.listen(3000)
16) Events
const EventEmitter = require('events'),
events = new EventEmitter()
events.on("app.started", () => {
console.log("App started")
events.emit("db.load.requested")
})
events.on("db.load.requested", () => {
console.log("Loading DB")
events.emit("db.loaded")
})
events.on("db.loaded", () => {
console.log("DB loaded")
})
events.emit("app.started")
17) Eventos com RabbitMQ
O exemplo fica muito parecido com o anterior, mas funciona em ambiente distribuido. Melhor ver direto no github
Exemplos das libs criadas pela AGCO
18) Harvester.js, baseado em Express
Framework para criar hypermedia apis com Mongo. Ele dá o CRUD completo, filtro, pesquisa em entidades conectadas, SSE, etc. É um fork do fortune.js
Dica: rodar o server abaixo + send.js. Daí é só chamar http://localhost:3000/orders?include=customer.country no browser.
'use strict';
const Types = require('joi'),
harvesterjs = require('harvesterjs'),
options = {
adapter: 'mongodb',
connectionString: 'mongodb://localhost:27017/agco',
oplogConnectionString: 'mongodb://localhost:27017/local',
inflect: true
},
app = harvesterjs(options)
app.resource('order',{
description: Types.required().description('Sample'),
links: {
customer: 'customer'
}
})
app.resource('customer',{
name: Types.required().description('Sample'),
links: {
country: 'country'
}
}).before('customer', function(req) {
return this
}).after('customer',function (req,res) {
return this
})
app.resource('country',{
name: Types.required().description('Sample')
})
app.listen(3000,function(){
console.log("App started on 3000")
})
19) Hapi-harvester
Similar ao harvester.js, mas baseado no Hapi.
'use strict'
// dependencies
const Hapi = require('hapi'),
Joi = require('joi'),
url = require('url'),
harvester = require('hapi-harvester'),
susie = require('susie')
// Configure hapi-harvester to use our dockerised instance of MongoDB
// Get the docker hostname
const dockerHostName = '127.0.0.1',
// use reference to docker host to create a connection URL to MongoDB
mongoDbUrl = 'mongodb://' + dockerHostName + '/test',
// use reference to docker hostname to create a connection URL to the oplog
mongoDbOplogUrl = 'mongodb://' + dockerHostName + '/local',
// configure the hapi-harvester adapter to use our dockerised MongoDB with replicasets enabled
adapter = harvester.getAdapter('mongodb')({
mongodbUrl: mongoDbUrl,
oplogConnectionString: mongoDbOplogUrl
})
//
// Start our hapi server with the hapi-harvester plugin
//
// create a hapi server
const server = new Hapi.Server()
// configure the port
server.connection({port: 3000})
// we need to register the hapi-harvester plugin before we can use it
server.register([
{
register: harvester, // the hapi-harvester plugin "required" from above
options: {
adapter // use the MongoDB adapter created above
}
},
susie ], // for streaming SSE
() => {
// Defining a model and default routes
// first we need to define a schema for our model, using Joi for
// validation
const brandSchema = {
type: 'brands',
attributes: {
code: Joi.string(),
description: Joi.string()
}
}
// next we need a reference to our plugin so we can add routes.
const harvesterPlugin = server.plugins['hapi-harvester']
// Using hapi-harvester's routes.all function we can add our schema and
// then add all routes for our model to the hapi server.
harvesterPlugin.routes.all(brandSchema).forEach((route) => {
server.route(route)
})
// finally we can start the server
server.start(() => {
console.log('Using MongoDB at:', mongoDbUrl)
console.log('Server running at:', server.info.uri)
})
}
)
Transpilers
20) Typescript
Exemplo básico com tipos, classes, etc
import * as express from "express";
const app = express();
app.get("/", (req, res) => {
res.send('Hello World!');
})
app.listen(3001, () => {
console.log('Example app listening on port 3001!')
})
class Person {
firstName: string;
lastName: string;
constructor(public fn:string,public ln:string){
this.firstName = fn;
this.lastName = ln;
}
}
var user = new Person("Marcelo","Adamatti");
21) CoffeeScript
Porquê alguns procuram ser ainda menos verboso.
number = 2
square = (x) -> x * x
22) ClojureScript
Esse exemplo está separado em vários arquivos, melhor olhar no github.
Relacionados
Dúvidas? Sugestões? Reclamações? Feedbacks? Aprendeu algo novo? Comenta aí!