History: Node.js and Heroku

Revision made 7 years ago by Francisco Presencia. Go to the last revision.

Let's learn how to use javascript to program a server based on Node.js and upload it so everyone can access our page. We will set it up so visitors can access several pages and perform several actions.

While the tools and libraries change from project to project, the methodology is somewhat similar: Start, Develop, Test and Deploy. Most of these can be partially automated.

Start project

If you followed the "the Getting started lesson you should have Node.js and NPM installed in the system.

First create a folder for our project. I will be using one in the location ~/projects/web, but you can set it up wherever you prefer. Then you should access it from the terminal. For *nix systems (Mac & Linux):

cd ~/projects/web

This will make all the successive commands to execute for your project. So let's get the first one, initialize the project with NPM:

npm init

Then you can follow the questions to set up your project. The answers are not really important, so don't overthink them or just use the defaults. After this is finished, you should see a file called "package.json" within your folder.

Now we will install the library server to make development easier:

npm install server --save

The --save option is there so that Node.js knows that we depend on this library when deploying the code later on.

If you happen to find any error with a weird message you can just Google it. Fortunately Node.js is mature enough so any installation/start error can be solvable with some searching around.

Node.js code

This is a mandatory "Hello world" program. Create a file called "app.js" with this content:

// Include the library "server"
const server = require('server');

// Retrieve a router for browser calls (explained later)
const { get } = server.router;

// Launch the server listening for "GET /"
server([
  get('/', ctx => ctx.res.send('Hello world'))
]);

If you don't understand the previous don't worry, it will be explained later on. It is one of the main problems with Node.js, the learning curve is quite steep.

Now we can see that message on the browser. Run it through nodemon (installed in the Getting started guide) and open http://localhost:3000/ in your browser

nodemon app.js

Exercise: create a "Hello world" page in another language. Tip: you do not need to restart nodemon, as it will be relaunched automatically.

Challenge: create a server that responds "Bye world" when the browser opens "http://localhost:3000/bye".

Routing

For this demo we copy our files (index.html, style.css, javascript.js and any asset) into a new folder inside our project called "public".

Now let's tell our server what files and how it should display. First let's just display "public/index.html". In app.js, write this instead of our previous 'Hello world':

const server = require('server');
const { get, post } = server.router;

// Create the route "home" for the method "GET" in the url "/"
const home = get('/', ctx => ctx.res.sendFile(__dirname + '/public/index.html'));

// Launch the server with the route home
server(home);

If we reopen http://localhost:3000/ and refresh it we can see our index.html without any style or javascript. The server will automatically know where our css and (front-end) javascript is, which is in the folder named "public" by default.

Only the specific routes will be shown from now on for brevity, but you should write the whole code as shown before.

Now let's create a route to get the file requested dynamically. We will get the url fragment as the variable name and then use it:

const page = get('/:name', ctx => {
  console.log(ctx.req.params);
  ctx.res.send(ctx.req.params);
});

The previous one only shows us in the terminal what file we are reading and on the browser the file name. Let's actually display the file in the browser:

const page = get('/:name', ctx => {
  var file = __dirname + '/' + ctx.req.params.name + '.html';
  ctx.res.sendFile(file);
});

Forms

Let's try to create a form and handle it with Node.js. First we create the form in "public/contact.html":

<!DOCTYPE html>
<html>
  <head>
    <title>Página Web</title>
    <meta charset="utf-8">
    <meta name='viewport' content='width=device-width, initial-scale=1'>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/picnicss/6.1.1/picnic.min.css">
    <link rel="stylesheet" href="style.css">
  </head>
  <body>

    <form action="/contacto" method="POST" class="contact">
      <h1>Contacto</h1>
      <input name="name" placeholder="Nombre">
      <input name="lastname" placeholder="Apellidos">
      <input name="age" placeholder="Edad">
      <input type="submit" value="Enviar">
    </form>

  </body>
</html>

With some style touches in "public/style.css":

.contact {
  width: 500px;
  margin: 100px auto;
}

.contact input {
  margin-top: 0;
  margin-bottom: 10px;
}

Now we can read the data on the server. So far we won't be storing it, just display it on the terminal:

const printData = post('/contact', ctx => {
  console.log(ctx.req.body);
  ctx.res.redirect('/');
});

If everything worked fine, this should display our data from the contact form and then redirect us home. We could change this for a "/thankyou" page or just handle it with javascript on the front-end and not redirect anywhere.

REST API

A REST API is a group of URLs and actions that can be done to them (GET, POST, PUT, DELETE). Let's say we have a user system. This would be a basic REST API for it:

GET /users            # Get all of the users data
GET /users/456546     # Get one specific user data
POST /users           # Create a new user
PUT /users/435465     # Edit an existing user
DELETE /users         # Remove all the users
DELETE /users/354643  # Remove a single user

This is useful on its own, but specially useful with mobile apps as well as webapps (React, Angular, etc). It separates our logic at some specific points and makes it easier to handle the complexity.

Let's make a small API that handles a Like button. In our app.js:

const server = require('server');
const { get, post, del } = server.router;

// An array with the ids of the things we liked
const likes = [];

// Retrieve all of our likes
const show = get('/likes', ctx => {
  ctx.res.json(likes);
});

// Create a new like
const add = post('/likes/:id', ctx => {
  let id = ctx.req.params.id;
  likes.push(id);
  ctx.res.json({ added: true });
});

// Remove a like
const remove = del('/likes/:id', ctx => {
  let id = ctx.req.params.id;
  let index = likes.indexOf(id);
  likes.splice(index, 1)
  ctx.res.json({ deleted: true });
});

server(show, add, remove);

Now we can have a list of items that can be liked:

<main>
  <ul>
    <li>First <button data-id="1">Like</button></li>
    <li>Second <button data-id="2">Like</button></li>
    <li>Third <button data-id="3">Like</button></li>
    <li>Fourth <button data-id="4">Like</button></li>
  </ul>
</main>

<script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
<script>
  // Load all the likes and add the class 'active' to the ones we liked
  fetch('/likes/').then(res => res.json()).then(likes => {
    likes.forEach(id => {
      $(`button[data-id='${id}']`).addClass('active');
    });
  });

  // Handle the button clicks
  $('button').on('click', function(e){
    var id = $(e.target).attr('data-id');
    var method = $(e.target).hasClass('button');
    fetch('/likes/' + id, { method: method });
  });
</script>

Exercise: make a dislike button that calls the route /dislike through POST and DELETE and shows on the browser console what the server is doing.

 

Deploy to Heroku

Then in a file called ".gitignore" we will write this. This will allow us for uploading only our own code while the hosting (Heroku) handles the dependencies:

node_modules