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 the website to Heroku.

First we install server, a small library to make development easier:

npm install server --save

Then we create our main file, "app.js", with this code:

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

server(
  get('/', ctx => ctx.res.send('Hello world'))
);

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

We will also install nodemon as a global package since it makes things easier. Run this in the terminal:

npm install nodemon -g

Now we can see that message on the server. Run nodemon and open http://localhost:3000/ in your browser:

nodemon

Routing

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

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

// Route
const home = get('/', ctx => ctx.res.sendFile(__dirname + '/index.html'));

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 a folder named "public" by default. We could change this and the server port in the options:

server({ port: 8080, public: './static' }, ...);

Note: only the Route part is 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 "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:

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

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

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

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

// Remove a like
let remove = del('/likes/:id', (req, res) => {
  let id = req.params.id;
  let index = likes.indexOf(id);
  likes.splice(index, 1)
  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.