Introduction

Express is a web application framework for Node.js. It's minimal, flexible and great for building Web APIs.

We can build any kind of server-side application that we want, however, remember that we talked about how low level is Node? So Express give us one layer of abstraction. We can define routes and controllers easily, and it give us the power to add validations and custom code, using everything that Node give to us.

Concepts Recap

URL

Before we start I would like to recap some HTTP basics. First, lets talk about URL. Each part of the URL represents an information to the server that it's important to define what it should do and execute. Here we explain some of the info that an URL can have.

HTTP URL
HTTP url eplained
  • Protocol: the protocol determines how the request will be transmitted. We will be dealing exclusively with http and https.

  • Host: the host identifies the server. Servers on your computer (localhost) or a local network may be simply one word, or it may be a numeric IP address. On the internet, the host will end in a top-level domain, like .com or .net.

  • Port: each server has a collection of numbered ports. Some port numbers are "special", like 80 and 443. If you omit the port, port 80 is assumed for HTTP and 443 for HTTPS. In general if you aren't using none of those, you should use a port number greater than 1023.

  • Path: the path is generally the first part of the URL that your app cares about. The path should be used to uniquely identify pages or other resources in your app.

  • Query string: the query string is an optional collection of name/value pairs. The querystring starts with a question mark (?), and name/value pairs are separated by "&".

  • Fragment: The fragment (or hash) is not passed to the server at all. It is strictly for use by the browser. It's becoming very common for single-page applications to use it.

HTTP

"HTTP (Hypertext Transfer Protocol) is an application protocol for distributed, collaborative, hypermedia information systems." - Wikipedia

It is just a protocol. A pretty important one, since this is the protocol that we use to surf on the web. Why do we have to know this protocol? Simple, we need to know which kind of responses this protocol have, so any client that connect to our server can understand all of our server's responses. Fortunately we have a cheat sheet for that. The most important ones are:

  • 200: Successful request;
  • 201: The request has been fulfilled and resulted in a new resource being created;
  • 400: The request could not be understood. Malformed syntax;
  • 401: The request requires an authenticated user;
  • 403: The server understood the request, but refused to fulfill it;
  • 404: Most famous code. The server could not found the page requested.

Now that we know each kind of responses we can understand how our clients can make their calls. We have eight different types of request that are defined here. However, for now, we have to know just 4:

  • GET: retrieve whatever information that was requested;
  • POST: sends an entity enclosed in the request. Used to send information;
  • PUT: sends an entity enclosed in the request to be stored by the server. Used to update information;
  • DELETE: request the server to delete the resource identified by the request.

We still have OPTIONS, HEAD, TRACE and CONNECT, but we will try not to use them for this classes, but it is good to know that they exist.

Hello World with Express

Lifting a Server

Alright ladies and gentlemen, now it is the time! We will learn what you all came here for! Express!

First things first. Lets install this bad boy:

$ npm install express
Installing Express Package

Sweet! No more talk! Here is some CODE:

'use strict';

const express = require('express');
const app = express();

app.get('/', function(request, response) {
  response.send('I\'m alive!');
});

app.listen(8080, function(){
  console.log('Listening on port 8080...');
});
Simple HTTP server with Express

Express Request and Responses

Neat, hun? Easy to read, simple and small. We can see events handlers being configured! This is the same behavior that we saw at Lecture 1: Node Server. Hey, hold on! Where is the end() event being called? Where are we writing the 200 status? Express technomagically does that for us. Nevertheless, if you want you can do this by yourself:

'use strict';

const express = require('express');
const app = express();

app.get('/', function(request, response) {
  response.status(200).send('I\'m alive!');
});

app.listen(8080, function(){
  console.log('Listening on port 8080...');
});
Setting Status For Yourself

We can send objects too. They will be stringfied and sent to the requester:

'use strict';

const express = require('express');
const app = express();

app.get('/', function(request, response) {
  response.status(200).json({msg: 'I\'m alive!'});
});

app.listen(8080, function(){
  console.log('Listening on port 8080...');
});
Sending an Object

Easy. So basically we created a server that only receives a GET request and returns a message. But we can be more fancy. What about read some query string from the request?

'use strict';

const express = require('express');
const app = express();

app.get('/', function(request, response) {
    const msgStrings = ['First String', 'Second String'];
    // request.query is the query string. In this case we will return the
    // String present on the position msgStrings[index].
    // To test your server, use http://localhost:8080/?index=0
    const messageIndex = request.query.index;
    if(messageIndex && messageIndex < msgStrings.length) {
        response.send(msgStrings[messageIndex]);
    } else {
        response.sendStatus(403);
    }
});

app.listen(8080, function(){
    console.log('Listening on port 8080...');
});
Retrieving Query String Data

Easy-peasy. Express process the request for us before it reaches our code using something called middlewares. This way he can prepare the request object to be easy to use. We will talk about middlewares in another time. Now lets focus on what we can do for responses and routing.

Routing

We didn't had much fun yet. Now, with Express, we can create a more useful server. Lets keep using the GET request type. How about define a server that plays with our old friend brian, remember him? Our dog from Lecture 1: Lim Po Kim. But this time we will change the Dog class a little bit:

'use strict';

const Dog = function(name) {

  let _name = name;

  this.whoIsAGoodBoy = function() {
    return _name + ' is a good boy!';
  };

  this.wiggle = function() {
    return _name + ' is wiggling.';
  };

  this.getName = function() {
    return _name;
  };

  this.setName = function(newName) {
    _name = newName;
  }

}

module.exports = Dog;
New Dog Class
Dog ES6 Class
Dog Object Composition

With this brand new Dog class we can create different routes in our server.

'use strict';

const express = require('express');
const Dog = require('./Dog');
const app = express();
const brian = new Dog('Brian');

app.get('/goodDog', function(request, response) {
  response.send(brian.whoIsAGoodBoy());
});

app.get('/wiggle', function(request, response) {
  response.send(brian.wiggle());
});

app.listen(8080, function(){
  console.log('Listening on port 8080...');
});
Adding New Route To Our Server
Doing it without Express
Doing it fancy without Express

With this setup we have a server hosted at localhost:8080 that have two routes:

Brian is in fact a good dog! He is such a good dog that we decided to give him a bride! And they lived happily ever after! So happy that they had puppies! Now it is too difficult to ask who is a good dog and which one we should expect to wiggle its tail. But fear not! Express will help us out here. We can define dynamic routing.

'use strict';

const express = require('express');
const Dog = require('./Dog');
const app = express();
let dogs = [
  new Dog('Brian'),
  new Dog('Tina Turner'),
  new Dog('Puppy 1'),
  new Dog('Puppy 2'),
  new Dog('Puppy 3')
];

app.get('/goodDog/:doggyId', function(request, response) {
  const doggyIndex = request.params.doggyId;
  if(doggyIndex && doggyIndex < dogs.length) {
    response.send(dogs[doggyIndex].whoIsAGoodBoy());
  } else {
    response.sendStatus(403);
  }
});

app.get('/wiggle/:doggyId', function(request, response) {
  const doggyIndex = request.params.doggyId;
  if(doggyIndex && doggyIndex < dogs.length) {
    response.send(dogs[doggyIndex].wiggle());
  } else {
    response.sendStatus(403);
  }
});

app.listen(8080, function(){
  console.log('Listening on port 8080...');
});
Dynamic Routing

With this setup we have the following URLs now:

  • localhost:8080/goodDog/_doggyId_: Where _doggyId_ could be replaced by a number like localhost:8080/goodDog/2. This will make puppy 1 says that he is the good dog;
  • localhost:8080/wiggle/_doggyId_: Where _doggyId_ could be replaced by a number like localhost:8080/wiggle/0. This will make Brian wiggle his tail.

Now we can easily see which dog is a good dog and who wiggle its tails! Nicely done. Now that we are good with the results lets try to use POST requests. This is a little tricky because Express makes it difficult to get the POST's data. We need NPM to the rescue! Lets install a module that will parse all the data for us!

$ npm install body-parser
Installing Body-Parser

Now, lets use this package. I still do not want to tell you guys what a middleware is, so hang tight! Just assume that this will solve our problems.

'use strict';

const express = require('express');
const Dog = require('./Dog');
const bodyParser = require('body-parser');
const parseUrlencoded = bodyParser.urlencoded({extend: false});
const app = express();
let dogs = [...];

//app.get()...

app.post('/dog', parseUrlencoded, function (request, response) {
  const data = request.body;
  if(data){
    const newDog = new Dog(data.name);

    dogs.push(newDog);

    response.status(201).send('Dog added! Brian loved it.');
  } else {
    response.sendStatus(403);
  }
});

app.listen(8080, function(){
  console.log('Listening on port 8080...');
});
Post Request

One use of the package Body-Parser is to get the data that was encoded to be transmitted and parse it to an usable object. It makes the attribute resquest.body available to us. Normally this body data comes from a HTML form or an ajax call.

We can say that the methods PUT and DELETE will take the same implementation as POST and GET, the only difference will be which method to call on your app. Lets take a look how those two would be implemented them.

...

app.put('/dog', parseUrlencoded, function(request, response){...});

app.delete('/dog/:doggyId', function(request, response){...});

...
Put and Delete Routes

Wait a minute! I have seen one of those routes before! The PUT method have the same path as the POST method. It is the same declaration. I think we can fix that. Our Express application has a object named Route. With it we can define our routes in other way.

...

const dogRoute = app.route('/dog');
dogRoute.post(parseUrlencoded,function(request, response){...})
dogRoute.put(parseUrlencoded, function(request, response){...})

...
Using Route Object

Hhhmmm.... Still not good. We are using the variable dogRoute too many times. Imagine if we have all method types related to this route? No, we should have a better way! In fact, we have, just use chaining.

...

app.route('/dog')
  .post(parseUrlencoded,function(request, response){...})
  .put(parseUrlencoded, function(request, response){...})

...
Chaining Route Calls

That is how I like it! One less variable! Clean code! Beautiful code! Smells like victory!

Exercises

Instructions

To start this exercises, please clone this repository's master branch.

Exercise 1:

Create a Express server that respond with 'Hello World' if I try to reach it.

Exercise 2:

Using the same server from Exercise 1 change the GET route to return the user's list. This route should expect a query string that can come empty or not. This query string should be named "q" and will be used to search users by name. If there is no query string you should return the whole list.

Exercise 3:

Using the same server from Exercise 1 create a POST route that creates a user. The data will come from a form action. You should validate you data, it should exist. If it does not the request should fail. This route's path should be "localhost:3000/users"

Exercise 4:

Using the same server from Exercise 1 create a GET dynamic route that get an user by ID. This ID will be captured by the path's variable. In case that the user does not exist, return and empty object. This route's path should be "localhost:3000/users/:userId"

Exercise 5:

Using the same server from Exercise 1 create a DELETE dynamic route that delete an user by ID. This ID will be captured by the path's variable. This route's path should be "localhost:3000/users/:userId"

Exercise 6:

Using the same server from Exercise 1 create a PUT dynamic route that change an user password by ID. This ID will be captured by the path's variable. The data will come from a form action. You should validate that, if there is missing any data, the request should fail.This route's path should be "localhost:3000/users/password/:userId"

Exercise 7:

Refactor your code to clean it up. Use Route to couple methods that have the same path.