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

Entre Express, Koa e Hapi, o Hapi é meu preferido.

'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

O Mocha é o JUnit do node;

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")
})

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

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");

Porquê alguns procuram ser ainda menos verboso.

number = 2

square = (x) -> x * x

Esse exemplo está separado em vários arquivos, melhor olhar no github.


Outros

23) ChatBot

Ver post completo aqui


Relacionados


Dúvidas? Sugestões? Reclamações? Feedbacks? Aprendeu algo novo? Comenta aí!