©  Caio Ribeiro Pereira 2016

Caio Ribeiro Pereira, Building APIs with Node.js , 10.1007/978-1-4842-2442-7_12

12. Building the Client-Side App: Part 1

Caio Ribeiro Pereira

(1)São Vicente - SP, São Paulo, Brazil

After building a REST API using Node.js using JavaScript ES6 language , for the final two chapters of this book, we’ll create a new project. This time it will be a front-end project using the best of JavaScript.

This book has devoted 80 percent of its content to back-end development, but now, we focus on the remaining 20percent, which is about front-end development. After all, we have an API, but we need a client-side app, because it’s the only way for users interact with our project.

For this reason, in these last two chapters, we are going to build an SPA application using only pure JavaScript. That’s right: only vanilla JavaScript! No front-end framework like Angular, Backbone, Ember, React, jQuery, or any others will be used; only the best of vanilla JavaScript, or perhaps I should say vanilla ES6.

Setting Up the App Environment

Our client-side app will be built under an object-oriented programming (OOP) paradigm using ES6 classes and Browserify to be able to use some front-end modules from NPM. Also, we are going to automate some tasks to build our application using only the npm alias command, like we used in the API project.

To kick off, let’s open the terminal to create a new project, which cannot be in the same API directory. This new project will be named ntask-web.

1   mkdir ntask-web
2   cd ntask-web
3   npm init

With the npm init command, we answer these questions to describe this new project.

After executing the npm init command, the file package.json is generated (see Figure 12-1). To organize this new project, we must create some directories in the project’s root, as follows.

  • public: For static files like CSS, fonts, JavaScript and images.

  • public/css: For CSS files only (we use Ionic CSS).

  • public/fonts: For font files (we use Ionic FontIcons).

  • public/js: For JavaScript files , here we’ll put a compiled and minified JavaScript file, generated from the src folder.

  • src/components: For components business logic.

  • src/templates: For components views.

A435096_1_En_12_Fig1_HTML.jpg
Figure 12-1. NTask Web generating package.json file

To create these folders, just run this command.

1   mkdir public/{css,fonts,js}
2   mkdir src/{components,templates}

Here is a list of all the modules that will be used in this project.

  • http-server: CLI for starting a simple HTTP server for static files only.

  • browserify: A JavaScript compiler that allows use of some NPM modules. We’ll use some modules that have JavaScript Universal codes (codes that works in back-end and front-end layers) and also allows loading using CommonJS pattern, which is the same standard as Node.js.

  • babelify: A plug-in for the browserify module to enable reading and compiling ES6 code in the front end.

  • babel-preset-es2015: The presets for Babel recognizes ES6 code via babelify.

  • tiny-emitter: A small module to create and handle events.

  • browser-request: A version of the request module focused for browsers, it is a cross-browser (compatible with all major browsers) and abstracts the whole complexity of an AJAX request.

Basically, we are going to build a web client using these modules, so let’s install them running these commands.

1   npm install [email protected] [email protected] --save
2   npm install [email protected] [email protected] --save
3   npm install [email protected] [email protected] --save

After the installation, modify package.json by removing the attributes main, script.test, and license, and add all the alias commands that will be necessary to automate the build process of this project.

Basically, we’ll perform these tasks:

  • Compile and concatenate all ES6 codes from the src folder via the npm run build command.

  • Initiate the server in port 3001 via the npm run server command.

  • Create the command npm start, which will run the npm run build and npm run server commands.

To apply these alterations, edit the package.json file as shown here.

 1   {
 2     "name": "ntask-web",
 3     "version": "1.0.0",
 4     "description": "NTask web client application",
 5       "scripts": {
 6       "start": "npm run build && npm run server",
 7       "server": "http-server public -p 3001",
 8       "build": "browserify src -t babelify -o public/js/app.js"
 9     },
10     "author": "Caio Ribeiro Pereira",
11     "dependencies": {
12       "babel-preset-es2015": "^6.5.0",
13       "babelify": "^7.2.0",
14       "browser-request": "^0.3.3",
15       "browserify": "^13.0.0",
16       "http-server": "^0.9.0",
17       "tiny-emitter": "^1.0.2"
18     }
19   }

Now, we need to again create the .babelrc file to set up the babel-preset-es2015 module. To do this simple task, create the .babelrc in the root folder using this code.

1   {
2     "presets": ["es2015"]
3   }

After this setup, let’s include some static files that will be responsible for the layout of our project. To save time, we use a ready CSS stylization, from the Ionic framework, a very cool framework with several mobile components ready to build responsive apps.

We won’t use the full Ionic framework, because it has a strong dependency on the Angular framework. We only use the CSS file and font icon package. To do this, I recommend you download the files from the following links.

Put their CSS files into the public/css folder and put the font files into public/fonts.

To start coding, let’s create the home page and the JavaScript code responsible for loading the main page just to test if everything will run fine. The main HTML is going to be responsible for loading the Ionic CSS stylization, the main JavaScript files, and also enough HTML tags to build the layout structure. To understand this implementation, create the file public/index.html using this code.

 1   <!DOCTYPE html>          
 2   <html>
 3     <head>
 4       <meta charset="utf-8">
 5       <title>NTask - Task manager</title>
 6       <meta name="viewport" content="width=device-width,initial-scale=1">
 7       <link rel="stylesheet" href="css/ionic.min.css">
 8       <link rel="stylesheet" href="css/ionicons.min.css">
 9       <script src ="js/app.js" async defer></script>
10     </head>
11     <body>
12       <header class="bar bar-header bar-calm">
13         <h1 class="title">NTask</h1>
14       </header>
15       <div class="scroll-content ionic-scroll">
16         <div class="content overflow-scroll has-header">
17           <main></main>
18           <footer></footer>
19         </div>
20       </div>
21     </body>
22   </html>
Note

There are two empty tags: <main></main> and <footer></footer>. All the page interaction will manipulate these tags dynamically using JavaScript codes from the src/components files that we will write soon.

To finish this section, create the src/index.js file. This will display a simple Welcome! message in the browser when the page is loaded. This will be modified soon; after all, we are going to create it just to test if our project will work.

1   window.onload = () => {
2     alert("Welcome!");
3   };

Now we already have a simple, but functional environment to run our NTask client-side app . To execute it, run the command npm start and go to http://localhost:3001.

If everything works fine , you’ll see the result shown in Figure 12-2.

A435096_1_En_12_Fig2_HTML.jpg
Figure 12-2. First NTask screen

Creating Sign-in and Signup Views

In this section, we create all the templates to be used in the application. The templates are basically HTML pieces of strings, manipulated via JavaScript, and are largely used on SPA projects. After all, one of the SPA concepts is to load once all static files and only transfer data frequently between client and server.

All transition screens (template’s transition) become a task for client-side apps, causing the server only to work with data and the client to pick up the data to put them into appropriate screens for users to interact in the application.

Our application is a simple task manager, which has a REST API with endpoints to create, update, delete, and list tasks, and register, find, and delete users. All templates will be based on these API features.

To start, let’s build the template that is going to be the sign-in and signup screens. Thanks to the Template String feature provided by ES6, it is possible to create strings with data concatenation in an elegant way using this syntax.

1   console.log(`Olá ${nome}!`);

With this, it’s no longer necessary to use any template engine framework, because we can easily create the templates using a simple function to return an HTML string concatenated with some data.

To understand this implementation better, let’s write the sign-in home page templates using the function render() to return an HTML string. Create the file src/templates/signin.js as shown here.

 1   exports.render = () => {
 2     return `<form>
 3       <div class="list">
 4         <label class="item item-input item-stacked-label">
 5           <span class="input-label">Email</span>
 6           <input type="text" data-email>
 7         </label>
 8         <label class="item item-input item-stacked-label">
 9           <span class="input-label">Password</span>
10             <input type="password" data-password>
11         </label>
12       </div>
13       <div class="padding">
14         <button class="button button-positive button-block">
15           <i class="ion-home"></i> Login
16         </button>
17       </div>
18     </form>
19     <div class="padding">
20       <button class="button button-block" data-signup>
21         <i class="ion-person-add"></i> Sign up
22       </button>
23     </div>`;
24   };

Now, to complete the flow, we also create the signup screen template. Create the file src/templates/signup.js, using this code.

 1   exports.render = () => {
 2     return `<form>
 3       <div class="list">
 4         <label class="item item-input item-stacked-label">
 5           <span class="input-label">Name</span>
 6           <input type="text" data-name>
 7         </label>
 8         <label class="item item-input item-stacked-label">
 9           <span class="input-label">Email</span>
10           <input type="text" data-email>
11         </label>
12         <label class="item item-input item-stacked-label">
13           <span class="input-label">Password</span>
14           <input type="password" data-password>
15         </label>
16       </div>
17       <div class="padding">
18         <button class="button button-positive button-block">
19           <i class="ion-thumbsup"></i> Register
20         </button>
21       </div>
22     </form>`;
23   };

We now already have two important screens of the application. Now, we have to create the interaction code for these screens, which will be responsible for rendering these templates and, especially, interacting with the users and communicating with the API.

Writing Sign-in and Signup Components

The codes for the template’s interaction are going to be put into the src/components folder, but before creating them, let’s explore two new JavaScript ES6 features: classes and inheritance. To write our front-end code to be well organized, we’ll create a parent class, which is going to have only two important attributes as inheritance for all components that extend it: this.URL (contains the API URL address) and this.request (contains the browser-request module loaded).

Another detail of this parent class is that it will inherit all functionalities from the tiny-emitter module (via the class NTask extends TinyEmitter declaration), and these functionalities will be passed to their child classes as well, allowing our components to be able to listen and trigger events among the other components. To understand this class better, create the file src/ntask.js following this code.

 1   import TinyEmitter from "tiny-emitter";
 2   import request from "browser-request";
 3  
 4   class NTask extends TinyEmitter {
 5     constructor() {
 6       super();
 7       this.request = request;
 8       this.URL = "https://localhost:3000";
 9     }
10   }
11  
12   module.exports = NTask;

Now that we have the parent class NTask, will be possible to create the component’s classes. Not only will they have specific functionalities, but also attributes and generic functions inherited by the parent class. The components, instead of double or triple the codes , will simply reuse the codes.

Let’s create our first component for the sign-in screen. All component classes of our application will receive from the constructor the body object. This body is basically the DOM object from the tag <main> or <footer>. All components will inherit from the parent class NTask, so the execution of the function super() is required on the child class’s constructor. We’ll use the methods: render() (to render a template) and addEventListener() (to listen and treat any DOM components from the templates) to organize our components.

In this case, we will have two encapsulated events in the methods: formSubmit() (responsible for performing user authentication on the API) and signupClick() (responsible for rendering the sign-up screen in the browser).

Each component must emit an event via the function this.emit("event-name"), because we will create an observer class to listen and delegate tasks among the application’s components. A good example of a task that is going to be widely used is the template’s transitions, which occurs when a user clicks on a template’s button. The click event triggers the observer class to listen and delegate a task for a new template to be rendered in the browser.

To better understand these rules, create the file src/components/signin.js following this code.

 1   import NTask from "../ntask.js";
 2   import Template from "../templates/signin.js";
 3
 4   class Signin extends NTask {
 5     constructor(body) {
 6       super();
 7       this.body = body;
 8     }
 9     render() {
10       this.body.innerHTML = Template.render();
11       this.body.querySelector("[data-email]").focus();
12       this.addEventListener();
13     }
14     addEventListener() {
15       this.formSubmit();
16       this.signupClick();
17     }
18     formSubmit() {
19       const form = this.body.querySelector("form");
20       form.addEventListener("submit", (e) => {
21         e.preventDefault();
22         const email = e.target.querySelector("[data-email]");
23         const password = e.target.querySelector("[data-password]");
24         const opts = {
25           method: "POST",
26           url: `${this.URL}/token`,
27           json: true,
28           body: {
29             email: email.value,
30             password: password.value
31           }
32         };
33         this.request(opts, (err, resp, data) => {
34           if (err || resp.status === 401) {
35             this.emit("error", err);
36           } else {
37             this.emit("signin", data.token);
38           }
39         });
40       });
41     }
42     signupClick() {
43       const signup = this.body.querySelector("[data-signup]");
44       signup.addEventListener("click", (e) => {
45         e.preventDefault();
46         this.emit("signup");
47       });
48   }
49   }
50  
51   module.exports = Signin;

Let’s also create the Signup class. This will follow the same pattern as the Signin class. To see how it should be, create the src/components/signup.js file as shown here.

 1   import NTask from "../ntask.js";
 2   import Template from "../templates/signup.js";
 3
 4   class Signup extends NTask {
 5     constructor(body) {
 6       super();
 7       this.body = body;
 8     }
 9     render() {
10       this.body.innerHTML = Template.render();
11       this.body.querySelector("[data-name]").focus();
12       this.addEventListener();
13     }
14     addEventListener() {
15       this.formSubmit ();
16     }
17     formSubmit() {
18       const form = this.body.querySelector("form");
19       form.addEventListener("submit", (e) => {
20         e.preventDefault();
21         const name = e.target.querySelector("[data-name]");
22         const email = e.target.querySelector("[data-email]");
23         const password = e.target.querySelector("[data-password]");
24         const opts = {
25           method: "POST",
26           url: `${this.URL}/users`,
27           json: true,
28           body: {
29             name: name.value,
30             email: email.value,
31             password: password.value
32           }
33         };
34         this.request(opts, (err, resp, data) => {
35           if (err || resp.status === 412) {
36             this.emit("error", err);
37           } else {
38             this.emit("signup", data);
39           }
40         });
41       });
42     }
43   }
44  
45   module.exports = Signup;

To finish this initial flow, we still have to create the observer class and then, load it inside the src/index.js file. This will be the code responsible for starting all the interactions among the application’s components. The observer class will be called App. This constructor will perform the instance of all components, and will have two main methods: init() (responsible for starting the component’s interactions) and addEventListener() (responsible for listening and treating the component’s events). To understand this implementation , let’s create the src/app.js file, following this code.

 1   import Signin from "./components/signin.js";
 2   import Signup from "./components/signup.js";
 3
 4   class App {
 5     constructor(body) {
 6       this.signin = new Signin(body);
 7       this.signup = new Signup(body);
 8     }
 9     init() {
10       this.signin.render();
11       this.addEventListener();
12     }
13     addEventListener() {
14       this.signinEvents();
15       this.signupEvents();
16     }
17     signinEvents() {
18       this.signin.on("error", () => alert("Authentication error"));
19       this.signin.on("signin", (token) => {
20         localStorage.setItem("token", `JWT ${token}`);
21         alert("You are logged in!");
22       });
23       this.signin.on("signup", () => this.signup.render());
24     }
25     signupEvents() {
26       this.signup.on("error", () => alert("Register error"));
27       this.signup.on("signup", (user) => {
28         alert(`${user.name} you were registered!`);
29         this.signin.render();
30       });
31     }
32   }
33
34   module.exports = App;

Basically, it created the component’s events for successful authentication, authentication error, accessing the signup screen, successful signup, and signup error. The init() method starts the home page, which is the signin screen, and then executes the addEventListener() method to listen to all the component events that are encapsulated inside the methods signinEvents() and signupEvents().

To finish this chapter, edit the src/index.js file to be able to load and initiate the App class inside the window.onload() event and then start the interactive flow of the application.

1   import App from "./app.js"
2
3   window.onload = () => {
4     const main = document.querySelector("main");
5     new App(main).init();
6   };

Let’s test it. If you followed these implementations step by step, you’ll have a basic sign-in and signup flow. To run this application, first, open two terminals: one for the API server and the other for the client-side app.

On both terminals, you must run the command npm start. Now these applications will be available in the addresses:

  • NTask API: https://localhost:3000

  • Ntask Web: http://localhost:3001

As we are not using a valid digital certificate for the production environment, it is quite probable that your browser will block the first attempt to access the API. If that happens, just open it in the browser: https://localhost:3000.

Next, add an exception in your browser to enable the API access. See Figure 12-3 to understand how to add an exception in the Mozilla Firefox browser, for example.

A435096_1_En_12_Fig3_HTML.jpg
Figure 12-3. Adding an exception for the API address

Now that the API has full access, go to the NTask web address at http://localhost:3001.

If everything is right, you’ll be able to access the screens shown in Figures 12-4 through 12-7.

A435096_1_En_12_Fig4_HTML.jpg
Figure 12-4. Sign-in screen
A435096_1_En_12_Fig5_HTML.jpg
Figure 12-5. User signup screen
A435096_1_En_12_Fig6_HTML.jpg
Figure 12-6. Registering a new user
A435096_1_En_12_Fig7_HTML.jpg
Figure 12-7. Logging in with a new account

Conclusion

Our new project is taking shape, and we are closer to building a functional system for real users, all of this integrating the client application with the API that was built in the previous chapters.

In this chapter, we created only the environment and some screens, enough to structure the application. Keep reading, because in the next chapter we’ll go deeper until we finish this project.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset