express-js-presentation



express-js-presentation

0 1


express-js-presentation


On Github hackerone / express-js-presentation

ExpressJS - everything is a middleware

Ganesh - @hackerone

Web developer

what is expressJS

  • Minimalist web application framework
  • Runs on NodeJS
Express JS is a minimalist NodeJS web application framework. It has robust set of features for web and mobile application development.

what's nodeJS

  • Javascript runtime
  • Built on Chrome's V8 javascript engine.
  • Event driven, non-blocking IO model
  • Large package eco-system, npm!
Before we jump into ExpressJS, let's take a look at what NodeJS is quickly, this will help us understand why expressJS helps. NodeJS is a javascript runtime built on chrome's V8 javascript engine. It uses an event driven and non-blocking IO model which makes it lightweight and efficient. It also means that most of the operations are async and we'll be dealing with callbacks often. It has a large package eco-system, npm. So, most libraries you'll need for building a web app are built by someone already.

starting a server in node.JS

    var http = require('http');

    http.createServer((req, res) => {
        res.write('hello world');
        res.end();
    }).listen(3000);

Writing a simple server hello world in NodeJS is pretty straight forward. NodeJS API provides a built-in http module, which let's you create a server. The createServer takes in a callback, which gets 2 parameters request and response. as the name suggests, request gives you info about the request and response helps us write a response, in most web apps to the browser. And here we listen to port 3000.

starting a server in node.JS

    curl http://localhost:3000

    hello world

When you point your browser at localhost port 3000, you'll get an hello world. it's as simple as that.

starting a server in node.JS

    var http = require('http');

    http.createServer((req, res) => {
        if(req.url == '/') {
            res.write('hello world');
        } else if(req.url == '/tom'){
            res.write('hello tom')
        } else if(req.url == '/joe'){
            res.write('hello joe')
        }
        res.end();
    }).listen(3000);

But a web app has multiple end points or pages. So,when we need more URLs, we tend to add a logic inside the callback and that makes the code a bit ugly and makes it un-maintainable. We need an abstracted layer which let's us build web apps in a more manageable way.

Routing middleware

    npm install express

    var http = require('http'),
        express = require('express');
    var app = express();

    app.use('/tom', (req, res, next) => {
        res.write('hello tom');
        res.end();
    });

    app.use((req, res, next) => {
        res.write('hello world');
        res.end();
    });

    http.createServer(app).listen(3000);

This is where express comes in. Express provides an abstraction here. instead of passing your method, you can pass the express instance to the http createServer method, and express will handle your request from there. let's get into the code here. npm install express will download the express module and it's dependencies into your project. so you can then require it from your code. Express uses concept of middlewares to control the flow of the request. we'll look at middlewares a bit later. but for now, app.use will add middlewares to the app's request flow. If you look at the code there, the first middleware which returns hello tom will get executed for '/tom'. There is case which doesn't have a URL parameter, this will get executed for all cases, other than tom.

Routing

    curl http://localhost:3000/tom
    hello tom

    curl http://localhost:3000/mary
    hello world

    curl http://localhost:3000/
    hello world

Routing continued...

    app.get('/tom', (req, res, next) => {
        res.write('hello tom');
        res.end();
    });

    app.post('/tom', (req, res, next) => {
        res.write('creating tom...');
        res.end();
    });

In most web apps, you would want to differentiate between the type of Request, for a typical website you'd want to handle a GET and a POST differently. and for a RESTful service, you'd want to support PUT and DELETE and sometimes pre-flight HEADs too. Express routing supports this. So instead of using app.use, we could use app.get and app.post

Routing continued...


    curl http://localhost:3000/tom
    hello tom

    curl -X POST http://localhost:3000/tom
    creating tom...

So, when you make a GET request to the url, you get hello tom, while a post will return "creating tom..."

Advanced Routing

    app.get('/:name', (req, res) => {
        var name = req.params.name;
        res.write('hello '+ name);
        res.end();
    });
ExpressJS Routing mechanism doesn't stop here. we can pass parameters in the URL's and can use them in functions. so, the code above will match /tom /mary /joe /everyone and prints "hello "+ name

Advanced routing

    curl http://localhost:3000/tom
    hello tom

    curl http://localhost:3000/joe
    hello joe

    curl http://localhost:3000/mary
    hello mary

So, we've added support for more names in the URL.

Advanced routing

    app.get('/:name(\\w{3})', (req, res) => {
        var name = req.params.name;
        res.write('hello '+ name);
        res.end();
    });
Routing even supports regex matching. So i can be rude and say hello only to people who's names are exactly 3 letters long.

Advanced routing

    curl http://localhost:3000/tom
    hello  tom

    curl http://localhost:3000/joe
    hello  joe

    curl http://localhost:3000/mary
    hello world

Routing module

    var express = require('express');
    var router = express.Router();

    router.get('/profile', (req, res) => {
        res.write('profile');
        res.end();
    });

    router.get('/status', (req, res) => {
        res.write('active');
        res.end();
    });

    app.use('/user', router);

When the app grows, you'd want to manage the routes in a modular way. And express provides a separate smaller router component to achieve this. In case of the example above, we're adding a sub-router for /user so, any url's that has /user as the first part will be handed over to the router.

Routing module

    curl http://localhost:3000/user/profile
    profile

    curl http://localhost:3000/user/active
    active


Routing module

    var express = require('express'),
        user = require('./user'),
        book = require('./book');

    var app = express();

    app.use('/user', user);
    app.use('/book', book);

In a bigger application, you can separate the routing logic and create controllers and bootstrap the controllers like above. In the above case, book would export a book router and will handle all the urls starting with /book. while the user router will handle all the urls starting with user.

Views

  • Express doesn't bundle any template engines
  • But it supports 14+ NodeJS template engines
how often do we see web pages in plain text? most apps we build usually should return html or json. To keep thing simple, ExpressJS team decided not to support any specific template engines, so how do they support 14+ template engines.

Views

    var express = require('express'),
        cons = require('consolidate'),
        app = express();

    app.engine('html', cons.handlebars);
    app.set('view engine', 'html');
    app.set('views', __dirname + '/views');

    app.get('/', (req, res) => {
        res.render('index', {
            name: 'world'
        });
    });

ExpressJS uses consolidate.js library to support template engines. And since consolidate.js suppports about 14+ NodeJS template engines, they can be utilized in the express app. let's go through the snippet, then we call app.engine method, which tells express use handlebars template engine to parse all the html files. we call app.set, which sets let's you manage express config. then we set 'view engine', 'html' which is the engine we just defined and 'views' to the absolute path of directory which contains all the view files. then inside our middleware, we call res.render, which takes in 2 parameters, the view and the data that needs to be passed to the view. the template engine, in our case handlebars will render the view file with the data passed.

Views

    // index.html
    <html lang="en">
        <head>
            <meta charset="UTF-8">
            <title>Hello {{name}}</title>
        </head>
        <body>
            <h1>Hello {{name}}!</h1>
        </body>
    </html>
And that'll render process the handlebars template and renders the output to the client. Consolidate provides support for most of the famous template engines like jade, handlebars, nunjucks. so you choose whatever flavour you like. Also, all the layout options and the way you manage layouts will be handled by the template engine.

Other request / response methods

    res.json({
        message: 'hello',
        name: 'world'
    });

    res.redirect('hello/world');

    res.redirect(301, 'hello/world');

Apart from the views, express also offers a bunch of response helper methods for returning a json or redirect header and cookies etc.

and that's what comes packed with expressJS!

But...

  • we need support for cookies
  • we need to be able to parse data sent in POST
  • we need user authentication
  • we need access to databases
  • and more...
to build a typical web app, we need more features. like we need to parse cookies, parse POST data handle authentication and have access to databases. this is where the middleware magic kicks in. so let's take a quick look about what a middle ware is.

Middlewares

    app.use((req, res, next) => {
        req.name = 'tom';
        next();
    });

    app.use((req, res, next) => {
        console.log(req.name); // tom
        res.end();
    });

    app.use((req, res, next) => {
        // this middleware will not be run
        console.log('hello'); 
        res.end();
    });

middleware is a function, that alters the flow of the request by modifying the request / response variables. it gets 3 parameters, the request, the response and next. request will have all the request info like the url, headers etc. response parameter will let you write / output your response. the third parameter next, calling next will execute the next middleware in the stack. calling app.use will let you add middleware to the request stack. so whenever your app get a request, all the middlewares that match the request will get executed. if you look at the example, we have 3 middlewares added to the flow, the first one adds a key called 'name' and set's it a value 'tom'. since it calls next(). the next middleware in the stack will get executed. in this case, the second middleware prints the name set by the first middleware and it ends the request. since the request end in the second middleware and the it doesn't call next, the third middleware does not get called.

Body parser middleware

    var bodyParser = require('body-parser');

    app.use(bodyParser.json());
    app.use(bodyParser.urlencoded({ extended: true }));

    app.post('/', (req, res) => {
        console.log(req.body);
    });

But we don't have to write our own middlewares, there's a large number of middlewares out there which can let you accomplish most common tasks. The express team themselves support a few essential middlewares. Since expressJS on its own cannot parse the content of a POST or a cookie, it requires a middle-ware. For example, the body parser.

Cookie parser middleware

    var cookieParser = require('cookie-parser');

    app.get('/', (req, res) => {
        console.log(req.cookies); // undefined
    });

    app.use(cookieParser());

    app.get('/', (req, res) => {
        console.log(req.cookies);
    });

This is a cookie parser middleware for getting the cookie information from the request header.

Middlewares for Auth

  var auth = (req, res, next) => {
    if(req.headers['let-me-in'] == true) {
      next();
    } else {
      res.render('error');
    }
  }

  app.use(auth);

  app.get('/', (req, res, next) => {
    res.render('index');
  });

Middlewares can also be used for authentication, for example you can have an auth middleware which checks if an auth header is present and blocks the flow.

things to look out for in middleware

  • the order of the middleware matters
  • middlewares must next or res.end
  • can have error handling middleware

Database

    var mongoose = require('mongoose');
    mongoose.connect('mongodb://localhost/library');

    var Book = mongoose.model('Book', { name: String });

    app.post('/book', (req, res) => {
        var book = new Book({
            name: req.body.name
        });

        book.save((err) => {
            if(err) // handle it
            res.redirect('/book/'+book._id);
        })
    });

    app.get('/book/:id', (req, res) => {
        Book.findOne({_id: req.params.id}, (err, book) => {
            if(err) // handle it
            res.render('book', {book: book});
        });
    });

You can use expressjs with any of the nodejs database drivers. Or you could use an odm like mongoose. using an ODM gives you some cool features like input validation, pre-save post-save events and a bunch of cool features. Things that we'll need to care about, when handling database operations is - all the database operations are asynchronous - we'll be getting into the callback sooner than we think.

Database with promise

    var mongoose = require('mongoose');
    mongoose.connect('mongodb://localhost/library');

    var Book = mongoose.model('Book', { name: String });

    app.post('/book', (req, res) => {
        var book = new Book({
            name: req.body.name
        });

        var operation = book.save();

        operation.then((book) => {
            res.redirect('/book/'+book._id);
        });

    });
    app.get('/book/:id', (req, res) => {
        var query = Book.findOne({_id: req.params.id});

        query.then( (book) => {
            res.render('book', {book: book});
        });
    });

Try to use promise whenever possible. this makes the code maintainable.

tips / anti-patterns

  • Use promises
  • do not put your DB connection inside middleware
  • think before you write your own middleware
  • use a process manager in production
we're dealing with an asynchronous flow in here, using promises will help you keep the code maintainable.

Do I have to write everything myself?


    npm install yo -g

    npm install generator-express -g

    yo express

    // and you'll have an app running in seconds!

So, we saw a bunch of things about expressJS. most are essential for any web app. So, do i have to write everything myself? Luckily not, people have come up with Yeoman generator, which scaffolds the app with all the essentials including templates and database. use them! but don't trust them blindly, read through the code they generate

thanks!

ExpressJS - everything is a middleware Ganesh - @hackerone Web developer