Node.js Web Frameworks

- By Me

Today

Topics

  • History
  • Numbers
  • TL; DR
  • Hello World
  • Routing
  • Validating parameters

Topics

  • History
  • Numbers
  • TL; DR
  • Hello World
  • Routing
  • Validating parameters

Started in 2009 from TJ Holowaychuk

In 2014 sponsorship passes to Strongloop

In 2016 Express goes under the Node Foundation umbrella

In 2015 IBM acquired Strongloop

Started in 2013 from TJ Holowaychuk

$2M Investment

Created on August 6, 2011 by Eran Hammer a member of WalmartLabs

Initially dependent on Expressjs

{
  "name" : "hapi",
  "description" : "HTTP API Server framework based on Express",
  "version" : "0.1.1",
  "author" : "Eran Hammer-Lahav <eran@hueniverse.com>",
  "repository" : "git://github.com/walmartlabs/hapi",
  "main" : "index",
  "keywords": ["http", "mac", "authentication", "oauth", "api", "express"],
  "engines" : {"node" : ">=0.6.0"},
  "dependencies": {
    "express": "2.x.x",
    "mac": "0.x.x",
    "validator": "0.x.x",
    "emailjs": "0.x.x",
    "sugar": "1.1.x"
  }
}

Topics

  • History
  • Numbers
  • TL; DR
  • Hello World
  • Routing
  • Validating parameters

23,286 Stars

"version": "4.13.4"

25 dependencies

8000+ dependents

9,019 Stars

"version": "1.1.2"

23 dependencies

400+ dependents

5,515 Stars

"version": "13.0.0"

19 dependencies

300+ dependents

Topics

  • History
  • Numbers
  • TL; DR
  • Hello World
  • Routing
  • Validating parameters

Express.js is the minimal/simplest one with a wide community provided middlewares for all kind of purposes

Minimal too with a "futuristic" look, based upon es6 generator functions making your code easier to argue about with its synchronous looking, async code

Hapi.js is the rails like framework with a configuration orientation that when everything is configured things will "fall" in place (strong point is also Joi for validation of all kind of payloads)

Topics

  • History
  • Numbers
  • TL; DR
  • Hello Worlds
  • Routing
  • Validating parameters
// npm install --save express
var app = require("express")();
var port = 3000;

app.get("*", (req, res) => res.send("Hello World!"))
 
app.listen(port, () => { 
    console.log("Listening in port " + port); 
});
// npm install --save koa
var koa = require('koa');
app = koa();

app.use(function *(){
  this.body = 'Hello World';
});

app.listen(3000);
const Hapi = require('hapi');

const server = new Hapi.Server();
server.connection({ port: 3000 });

server.start((err) => {

    if (err) {
        throw err;
    }
    console.log('Server running at:', server.info.uri);
});

Topics

  • History
  • Numbers
  • TL; DR
  • Hello Worlds
  • Routing
  • Validating parameters

Has a built in router

var express = require("express");
var app = express();

var userRouter = express.Router();

// The authentication middleware will be used
// only for the routes attached to the userRouter

userRouter.use(userAuthenticationMiddleware);

userRouter.get("/login", userController.login);
userRouter.get("/logout", userController.logout);
userRouter.get("/register", userController.renderregister);
userRouter.post("/register", userController.register);

app.use("/user", userRouter);

Has no built in router

//npm install koa-route
var koa = require("koa");
var router = require("koa-route");

var app = koa();

// in the controller function 
// this is bound to the application context
app.use(router.get("/user/login", userController.login));
app.use(router.get("/user/logout", userController.logout));
app.use(router.get("/user/register", userController.register));

app.listen(3000);

Has built in router

Can have named, optional and repeating parameters

server.route({
    // optionally we can respond 
    // to multiple http request types
    method: ['GET'/* 'POST' */],
    path: '/user/login',
    handler: function (request, reply) {
        reply('Hello!');
    }
});

Named parameters

server.route({
    // optionally we can respond 
    // to multiple http request types
    method: ['GET'/* 'POST' */],
    path: '/user/{name}',
    handler: function (request, reply) {
        // we can retrieve the name as follows
        var name = request.params.name;
        reply('Hello!');
    }
});

Named and optional parameters

server.route({
    // optionally we can respond 
    // to multiple http request types
    method: ['GET'/* 'POST' */],
    path: '/user/{name?}',
    handler: function (request, reply) {
        // we can retrieve the name as follows
        var name = request.params.name || 'John Doe';
        reply('Hello!');
    }
});

Named and repeating parameters

server.route({
    // optionally we can respond 
    // to multiple http request types
    method: ['GET'/* 'POST' */],
    path: '/user/{names*2}',
    handler: function (request, reply) {
        // for a request to /user/john/doe
        // we can retrieve the names as follows
        var first_name, last_name;
        [first_name, last_name] = request.params.name.split("/");
        reply("Hello ${first_name} ${last_name}!");
    }
});

Topics

  • History
  • Numbers
  • TL; DR
  • Hello Worlds
  • Routing
  • Validating parameters

Best place to validate is within the app.param method

app.get("/user/:user_id", (req, res) => {/**/})

app.param("user_id", (req, res, next, user_id) =>{
    if(user_id.match(/\D/g)){
        return next(new Error("Only numbers allowed for user id");
    }
    
    next();
})

Validating with an external library

// npm install --save validator
var validator = require("validator");
app.get("/user/:user_id", (req, res) => {/**/})

app.param("user_id", (req, res, next, user_id) =>{
    if(!validator.isInt(user_id)){
        return next(new Error("Only numbers allowed for user id");
    }
    
    next();
})

Doesn't have a built in way to handle payload validation

You have to include a validation library

var koa = require('koa');
var app = koa();

app.use(require('koa-body')());
app.use(require('koa-validate')());
app.use(require('koa-router')(app));
app.post('/signup', function * () {
    this.checkBody('name').optional().len(2, 20,"are you kidding me?");
    this.checkBody('password').notEmpty().len(3, 20).md5();
    //empty() mean this param can be a empty string.
    this.checkBody('nick').optional().empty().len(3, 20);
    yield this.checkFile('icon')
                .notEmpty()
                .size(0,300*1024,'file too large')
                .move("/static/icon/" , function*(file,context){
                    //resize image
                });
    if (this.errors) {
        this.body = this.errors;
        return;
    }
    this.body = this.request.body;
});
var Joi = require('joi');

var schema = Joi.object().keys({
    username: Joi.string().alphanum().min(3).max(30).required(),
    password: Joi.string().regex(/^[a-zA-Z0-9]{3,30}$/),
    access_token: [Joi.string(), Joi.number()],
    birthyear: Joi.number().integer().min(1900).max(2013),
    email: Joi.string().email()
}).with('username', 'birthyear').without('password', 'access_token');
var Joi = require('joi');

var schema = Joi.object().keys({
    username: Joi.string().alphanum().min(3).max(30).required(),
    password: Joi.string().regex(/^[a-zA-Z0-9]{3,30}$/),
    access_token: [Joi.string(), Joi.number()],
    birthyear: Joi.number().integer().min(1900).max(2013),
    email: Joi.string().email()
}).with('username', 'birthyear').without('password', 'access_token');

What's this?

var Joi = require('joi');

var schema = Joi.object().keys({
    username: Joi.string().alphanum().min(3).max(30).required(),
    password: Joi.string().regex(/^[a-zA-Z0-9]{3,30}$/),
    access_token: [Joi.string(), Joi.number()],
    birthyear: Joi.number().integer().min(1900).max(2013),
    email: Joi.string().email()
}).with('username', 'birthyear').without('password', 'access_token');

What's this?

var Joi = require('joi');

var schema = Joi.object().keys({
    username: Joi.string().alphanum().min(3).max(30).required(),
    password: Joi.string().regex(/^[a-zA-Z0-9]{3,30}$/),
    access_token: [Joi.string(), Joi.number()],
    birthyear: Joi.number().integer().min(1900).max(2013),
    email: Joi.string().email()
}).with('username', 'birthyear').without('password', 'access_token');

What's this?

var Joi = require('joi');

var schema = Joi.object().keys({
    username: Joi.string().alphanum().min(3).max(30).required(),
    password: Joi.string().regex(/^[a-zA-Z0-9]{3,30}$/),
    access_token: [Joi.string(), Joi.number()],
    birthyear: Joi.number().integer().min(1900).max(2013),
    email: Joi.string().email()
}).with('username', 'birthyear').without('password', 'access_token');

What's this?

Username has to be paired with birthyear

Password must not appear together with access_token

What's this?

server.route({
    method: 'GET',
    path: '/hello/{name}',
    handler: function (request, reply) {
        reply('Hello ' + request.params.name + '!');
    },
    config: {
        validate: {
            params: {
                name: Joi.string().min(3).max(10)
            }
        }
    }
});

The SKG Addition

Features

  • Declare routes in a json object
  • Define all errors in one place
  • Have your routes grouped
  • Validate your parameters
  • Easy to test with mock data
  • And more

Express Happiness

Routes

routes: {
    a: {
        subRoutes: {
            b: {

                // here goes the GET /a/b route definition
                get: {  },

                // here goes the POST /a/b route definition
                post: {  },

                // here goes the GET /a/b/c route definition
                subRoutes: {
                    c: { get: {  } }
                }
            }
        }
    }
}

Express Happiness

Routes for /users/:id

{routes: {
    users: {
        get: {...},
        subRoutes: {
            ":id": {

                // get user's info
                get: {...},

                // create a new user
                post: {...},

                // create a new user
                put: {...},

                // create a new user
                delete: {...},

            }
        }
    }
}}

Express Happiness

Routes for /users/:id

get: {
    alias: 'user_get',
    description: "Get user's info",
    fields: [
    	// id will be favidated as 
    	// defined in our fields validation object
        fieldsLoader.getField('id')
    ]
}

Express Happiness

Routes for /users/:id

fieldsLoader.getField('id')

param: {
    "key": "id",
    "type": "int",
    "humanReadable": "User's id",
    "description": "The users id as saved on the DB",
    "mandatory": "true",
    "min": 0,
    "validationFailureTexts": {
        "mandatory": "Please provide the user's id",
        "min": "ID must be a positive number"
    }
}

Express Happiness

Error handling

'404': {
    log: true,
    sendToClient: {
        code: 404,
        data: 'Invalid route'
    }
}, 'my_custom_error_code': {
    log: false,
    sendToClient: {
        code: 500,
        data: 'There was an error fulfilling your request'
    }
}

Express Happiness

Error handling / Triggering

var err = new Error();
err.type = 'my_custom_error';
err.details = 'The details of the error';
return next(err);

From any middleware we can trigger an error

Express Happiness

Mocking results

  • Add the mock: true property on the route definition
  • Create a file returning the mocked data named <route_alias>.json

HOW TO

Express Happiness

Routes for /users/:id | Mocking

get: {
    alias: 'user_get',
    description: "Get user's info",
    fields: [
    	// id will be favidated as 
    	// defined in our fields validation object
        fieldsLoader.getField('id')
    ],
    mock: true
}

Express Happiness

Routes for /users/:id | Mocking

// in mocksfolder/user_get.json

module.exports = {
    first_name: "John",
    last_name: "Doe",
    age: 32
}

Express Happiness

Route groups

{routes: {
    users: {
        groups: ["authenticate_users"],
        get: {...},
        subRoutes: {
            ":id": {

                // get user's info
                get: {...},

                // create a new user
                post: {...},

                // create a new user
                put: {...},

                // create a new user
                delete: {...},

            }
        }
    }
}}

Express Happiness

Route groups

var ex_happi = new expressHappiness(app, router, {
    mockData:{
        enable: true,
        folder: 'mockdata',
        global: true
    },
    reusableFieldsFile: 'reusableFields.js',
    errorFile:'errors.log',
    errorsConfigurationFile: 'conf/errors.js',
    apiConfigurationFile: 'conf/restConf.js',
    controllersFile: 'controllerFunctions.js'
});

Create express happiness app

Express Happiness

Route groups

var UserAuthenticator = require("./UserAuthenticator");

var ex_happi.generate("/api", {

    // pre validation middlewares
    "authenicate_users": [UserAuthenticator.authenticate]
}, {
    // post validation middlewares
});


// UserAuthenticator.js

module.exports = {
    authenticate: function(req, res, next){
        // authenticate your user    
    }
}

Create express happiness app

Thank you