Introduction to Forms

In the previous chapter, we had a glimpse of how the posts will look like. The way I did it was to mock the users and the posts. In a real application, however, we would like to have the users to submit content to our blog. This is what I am going to cover on this blog post. How we can create forms using handlebars to accept content from our users.


Table of Contents:


You can find the final result of this chapter in Github.

Refactoring of our project

First, I would like to make some refactoring to our app. Instead of having everything in a single javascript file we should consider splitting our app into multiple files. Doing so, we can achieve modularity, and segregation of concerns since each file it would have a specific purpose. At the moment, app.js is doing everything. From initialising the express app, to starting the web server and listening to requests with everything in between.

So let's create a new folder called routes. Now, we would have two different routes. We need one route for the homepage and one route for the login form. As you can imagine, as this project evolves we would need multiple routes. Under routes we will create two files called index.js and login.js. The project structure should look like as below.

microblog/
	node_modules/
	routes/
		index.js
		login.js
	views/
		partials/
			navigation.hbs
		index.hbs
	app.js
	package.json
	package-lock.json

Index.js it will be responsible for rendering the homepage. Everything we had in app.js have to go under routes/index.js as shown below.

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

user = {
    username: 'conpiy'
}

posts = [
    {
        'author': { 'username': 'alex' },
        'content': `The PicoCoder's Node.js tutorial is cool.`
    },
    {
        'author': { 'username': 'joanna' },
        'content': 'I love javascript!'
    }
]

/* GET home page. */
router.get('/', (req, res, next) =>
    res.render('index', { title: 'PicoBlog', username: user.username, posts }));

module.exports = router;

Therefore app.js will only have the require setup for starting up the server and delegating the handling of requests to the routes. Below is the updated app.js file.

const express = require('express');
const hbs = require('hbs');
const app = express();
const indexRouter = require('./routes/index');

// view engine setup
app.set('view engine', 'hbs');
// Set handlebars partials folder
hbs.registerPartials(__dirname + '/views/partials');

app.use('/', indexRouter);

const port = 3000;
app.listen(port, () => console.log(`Server listening at: ${port}!`));

Now it looks a lot cleaner and it's a lot easier to work with.

Pro Tip: In an environment that multiple developers can work in the same project, it would be a lot easier to split everything into smaller modules instead of having huge modules that are doing more than one thing. With this not only you minimizing the complexity of your app but also enabling the parallelization of work from multiple engineers.

Let's create the user login form

Template

Create a new file called login.hbs in the views directory. The file should look like this

<html>

<head>
    <title>{{title}}</title>
</head>

<body>
    {{> navigation}}
    <h1>Login</h1>

    <form action="/login" method="post">
        <label>Username:</label>
        <input type="email" required />

        <label>Password:</label>
        <input type="password" required />
        <button>Log In</button>
    </form>
</body>

</html>

As we saw in the previous templates, we still need a title for our page, the navigation partial which will have our navigation menu and the login form. The form html element has an attribute called action and another one called method. The action attribute is responsible for the url that we want to send this information and the method is how we want to send that information. So far we have seen two different HTTP methods. We used GET for retrieving or getting information from the server and we will use POST to send information back to the server. You can find all the available HTTP methods here. The template when it renders will look like this.

Login page

In the screenshot above I included the network requests. As you can see, we are making 2 HTTP GET requests. The first one is a GET HTTP request to the /login endpoint and the second one is a GET HTTP request to the / endpoint for retrieving the favicon icon which we don't have yet. Also, you can see that the first request succeeded with 200 which means OK. So the server responded with OK everything looks good, you can have this html page. The second request however, was a 404 which means NOT FOUND and is a way for the server to tell to the browser that "Look, this resource that you are trying to retrieve or get is not here. You can find all available HTTP response codes here.

Backend

Now let's see how our node app would look like. We have 2 things to implement. The first one is when we ask for the login page the server should render the login.hbs file and the second one is to get back the data from the user.

Displaying the user login form

Similar to index.js the login.js will have the following code.

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

/* GET login page. */
router.get('/', (req, res, next) => {
    return res.render('login', { title: 'PicoBlog' });
});

module.exports = router;

We need to include express and we are going to use the router from express to register the new route. We are defining that when the user navigates to the login page render the login.hbs and pass to the template the object with title: 'PicoBlog'. You might wonder how express understands the login route because is not defined in this file. Here you can see that we are defining router.get('/', (...)) instead of router.get('/login', (...)) this is because I will define the route in the app.js file. So the app.js file will change to this.

const express = require('express');
const hbs = require('hbs');
const app = express();
const indexRouter = require('./routes/index');
const loginRouter = require('./routes/login');

// view engine setup
app.set('view engine', 'hbs');
// Set handlebars partials folder
hbs.registerPartials(__dirname + '/views/partials');

app.use('/login', loginRouter);
app.use('/', indexRouter);

const port = 3000;
app.listen(port, () => console.log(`Server listening at: ${port}!`));

In line 5 we have to import the login.js file and we called it loginRouter. In line 12 we are setting express to use the loginRouter when the user requests the /login endpoint. Also, an important note here is that we have to setup the most specific endpoints first. If we had the line 13 before line 12 then express would match with the indexRouter because it would have been before the indexRouter and / also matches /login. This is why is really important to have the most specific routes first.

Getting the actual user data

When a user will click the Login button on the login page, the login details will be send to the /login endpoint with an HTTP post method. In our backend we have to create that endpoint. To achieve that we have to add the following code to the login.js file.

/* POST login page. */
router.post('/', (req, res, next) => {
    console.log(req.body)
    return res.render('login', { title: 'PicoBlog' });;
});

This will register the endpoint to retrieve the user data. For now I just console log the user data but in the future this will be used to give users access to the website. After the console log statement I redirect the user to the same page. This might change in the future and redirect the user to the home page will make more sense.

One important thing is that we have to add a middleware to our application.

const express = require('express');
const hbs = require('hbs');
const app = express();
const indexRouter = require('./routes/index');
const loginRouter = require('./routes/login');

// view engine setup
app.set('view engine', 'hbs');
// Set handlebars partials folder
hbs.registerPartials(__dirname + '/views/partials');

// Middleware
app.use(express.urlencoded({ extended: true }));

// Routes
app.use('/login', loginRouter);
app.use('/', indexRouter);

const port = 3000;
app.listen(port, () => console.log(`Server listening at: ${port}!`));

In line 13 in the above code I added the urlencoded middleware. The middleware is a mechanism in express that intercepts the requests going to our endpoints. A typical example for a middleware is the authentication middleware that can check for the authentication / authorization token and either allow or deny an action to be performed by the user.

In our case we need this to parse the data in the body of the request made from the login page. If we don't add that middleware then the body of the request cannot be parsed. If you want to read more about build in express middleware you can look here. After the requests is parsed from that middleware is passed on to the next one. In this case the /login page.

Pro tip: You can write your our own custom middleware functions.

Form fields validation

To do form fields validation we are going to use a module called express-validator.

Step 1:

npm install express-validator

Step 2:

const { check } = require('express-validator/check')

With the above code we get the check function. This is an ES6 feature called object destructuring.

Step 3:

const express = require('express');
const router = express.Router();
const { check, validationResult } = require('express-validator/check')

/* GET login page. */
router.get('/', (req, res, next) => {
    return res.render('login', { title: 'PicoBlog' });
});

const validationRules = [
    check('password').isLength({ min: 6 }),
    check('email').isEmail(),
]

/* POST login page. */
router.post('/', validationRules, (req, res, next) => {
    console.log(req.body)
    console.log((validationResult(req).array()))
    return res.render('login', { title: 'PicoBlog' });;
});

module.exports = router;

This is how the final login.js file would look like. Here we are validating that the password has minimum length of 6 characters and the email is a valid email address. As a result when the user has submitted a password that is less than 6 characters we can detect that and send back an error message. If there is an error using the function on line 18 we get an object back with the description of the error.

[ { location: 'body',
    param: 'password',
    value: 'user',
    msg: 'Invalid value' } ]

This can be interpreted as, we have an error in the body of the request in the parameter password and the value is user. The error message is Invalid value. Therefore we can have custom logic to send back to the user the error message and instructions on how to fix it. I am not going to go into details for now but I will explain how to do it later. Here is the API for express-validator, if you want to look what is available and how to use it.

Congratulations, you have completed the chapter 3 of this tutorial. You are now one step closer to becoming a full stack developer and start building your one web applications. Stay motivated and ask anything. Thanks!

You can find the final result of this chapter in Github.

If you liked the tutorial, please consider subscribing to my blog. That way I get to know that my work is valuable to you and also notify you for future tutorials. Stay tuned!