REST API In Node.js Using ES6

This article explains how to create a REST API using JavaScript ES6 and fort.js framework.

Introduction

 
ES6 is the new standard of JavaScript and it provides so many new things like  classes, spread operators, etc. We can use the power of ES6 to create a server side app using node. This will help us to get rid of the common problem of node.js development, i.e., callback hell. We can use async-await, classes, etc. to develop apps which are future proof.
 
In this article , we will be developing a REST API in ES6 using fort.js — a server-side MVC framework for Node. FortJS enables you to write server-side code which is modular, secure, and pretty much beautiful & readable. Here is the documentation link — http://fortjs.info/
 
You can also write the code in TypeScript using fort.js. In fact, this article is nothing but an ES6 version of - Rest API In Node.js Using TypeScript And Fort.js . So, if you are interested in TypeScript, please have a look at that article.
 
Now, let me explain more about fort.js.
 
Let's start with the mysterious name - fort.js. FortJS works similarly to a real fort and is based on the application architecture of Fort. It provides modularity in the form of components . There are three components.
  1. Wall - This is a top level component & every HTTP request is passed through the wall first.
  2. Shield - This is available for controllers. You can assign a shield to controller & it will be executed before a controller is called.
  3. Guard -  This is available for methods inside controllers known as workers in fort.js. The guard will be called before the worker is called. 
Now, let's do some action and create a REST API using FortJS.
 
Code
 
You can download the source code of this article from - https://github.com/ujjwalguptaofficial/fortjs/tree/master/example/rest/javascript
 

Project SetUp

 
Fort.js provides a CLI - fort-creator . This helps you to set up the project and develop faster. I m going to use the CLI too. 
 
So perform the below steps sequentially.
  • Install the ForstJS globally  -  run the command "npm i fort-creator -g"
  • Create a new project  - run "fort-creator new my-app". Here “my-app” is the name of the app, you can choose any.
  • Enter into the project directory  -  "cd my-app"
  • Start the dev server  - run " fort-creator start"
  • Open the browser & type the URL  - “http://localhost:4000/”
You should see something like this in the browser.
 
 

REST

 
We are going to create a REST endpoint for entity user - which will perform the CRUD operations for the user such as adding user, deleting user, getting user, and updating user.
 
According to REST,
  1. Adding user - should be done using HTTP method "POST"
  2. Deleting user - should be done using HTTP method "REMOVE"
  3. Getting user - should be done using the HTTP method "GET"
  4. Updating user - should be done using HTTP method "PUT"
For creating an endpoint, we need to create a Controller. You can read about the controller here. Create a file user_controller.js inside the Controllers folder and copy the below code inside the file.
  1. import { Controller, textResult, DefaultWorker} from 'fortjs'      
  2. export class UserController extends Controller {      
  3.       @DefaultWorker()      
  4.       async default() {      
  5.           return textResult('you have successfully created a user controller');      
  6.       }      
  7. }    
In the above code,
  • We have created a class "UserController" which is extending another class Controller from fortjs.
  • We have created a method default which is returning some result by using the method textResult from fort.js. textResult return HTTP response with content-type 'text/plain'.
  • We have used a decorator DefaultWorker from fort.js. A worker makes the method visible so that it can be called using HTTP request (no worker means it just a function which is only available for this class). A default worker is a worker which add the route "/" for the target method. Please take a look at worker doc - http://fortjs.info/tutorial/worker/
We have created a controller but it's still unknown by fort.js. In order to use this controller, we need to add this to routes. Open routes.js inside root folder and add UserController to routes.
  1. import {DefaultController } from "./controllers/default_controller";    
  2. import { UserController } from "./controllers/user_controller";    
  3.     
  4. export const routes = [{    
  5.     path: "/default",    
  6.     controller: DefaultController    
  7. },{    
  8.     path: "/user",     
  9.     controller: UserController    
  10. }]    
You can see, we have added the path "/user" for UserController. It means when the url contains path "/user", UserController will be called.
Now open the URL — "localhost:4000/user". You can see the output which is returned from the default method inside “UserController”.
 
 

Service

 
Before moving further, let’s write a service code which will help us to do CRUD operations.
 
Create a folder “models” and then a file “user.js” inside the folder. Paste the below code inside the file.
  1. export class User {  
  2.     constructor(user) {  
  3.         this.id = Number(user.id);  
  4.         this.name = user.name;  
  5.         this.gender = user.gender;  
  6.         this.address = user.address;  
  7.         this.emailId = user.emailId;  
  8.         this.password = user.password;  
  9.     }  
  10. }  
Create a folder “services” and then a file “ user_service.js” inside the folder. Paste the below code inside the file.
  1. import {  
  2.     User  
  3. } from "../models/user"
  4.  
  5. const store = {  
  6.     users: [{  
  7.         id: 1,  
  8.         name: "durgesh",  
  9.         address: "Bengaluru india",  
  10.         emailId: "durgesh@imail.com",  
  11.         gender: "male",  
  12.         password: "admin"  
  13.     }]  
  14. }
  15.   
  16. export class UserService {  
  17.     getUsers() {  
  18.         return store.users;  
  19.     }
  20.   
  21.     addUser(user) {  
  22.         const lastUser = store.users[store.users.length - 1];  
  23.         user.id = lastUser == null ? 1 : lastUser.id + 1;  
  24.         store.users.push(user);  
  25.         return user;  
  26.     }
  27.   
  28.     updateUser(user) {  
  29.         const existingUser = store.users.find(qry => qry.id === user.id);  
  30.         if (existingUser != null) {  
  31.             existingUser.name = user.name;  
  32.             existingUser.address = user.address;  
  33.             existingUser.gender = user.gender;  
  34.             existingUser.emailId = user.emailId;  
  35.             return true;  
  36.         }  
  37.         return false;  
  38.     }
  39.   
  40.     getUser(id) {  
  41.         return store.users.find(user => user.id === id);  
  42.     }
  43.   
  44.     removeUser(id) {  
  45.         const index = store.users.findIndex(user => user.id === id);  
  46.         store.users.splice(index, 1);  
  47.     }  
  48. }  
The above code contains a variable store which contains a collection of users and the method inside the service do operations like  add, update, delete, and get on that store.
 
So now we have service, we need to write the code to use those service and create a REST API.
 

GET

 
We are going to create an endpoint for getting the user.
 
Let’s rename the default methods to “getUsers” which will return all users. Replace the code inside user_controller.js by below code.
  1. import { Controller, DefaultWorker, jsonResult } from 'fortjs'  
  2. import { UserService } from '../service/user_service';  
  3.   
  4. export class UserController extends Controller {  
  5.     @DefaultWorker()  
  6.     async getUsers() {  
  7.         const service = new UserService();  
  8.         return jsonResult(service.getUsers());  
  9.     }  
  10. }  
As you can see, we are using DefaultWorker since it makes the method visible for http request and adds route "/" with http method "GET". So all these things using one decorator.
 
Now open the URL - localhost:4000/user or you can use any HTTP client such as postman.
 
 
This method is only for HTTP method — “GET” (since we are using DefaultWorker). If you will call this same endpoint for methods other than “GET”, you will get the status code 405.
 

POST

 
We need to create a method, which will add the users and only works for HTTP method "POST". So now, “UserController” looks like this,
  1. import { Controller, jsonResult, DefaultWorker, HTTP_METHOD, HTTP_STATUS_CODE, Worker, Route } from 'fortjs'    
  2.       
  3. export class UserController extends Controller {    
  4.       
  5.       @DefaultWorker()    
  6.       async getUsers() {    
  7.           const service = new UserService();    
  8.           return jsonResult(service.getUsers());    
  9.       }    
  10.            
  11.       @Worker([HTTP_METHOD.Post])    
  12.       @Route("/")    
  13.       async addUser() {    
  14.           const user = {    
  15.               name: this.body.name,    
  16.               gender: this.body.gender,    
  17.               address: this.body.address,    
  18.               emailId: this.body.emailId,    
  19.               password: this.body.password    
  20.           };    
  21.           const service = new UserService();    
  22.           const newUser = service.addUser(user);    
  23.           return jsonResult(newUser, HTTP_STATUS_CODE.Created);    
  24.       }    
  25. }    
In the above code,
  • We have created a method "addUser" and added a decorator “Route” with parameter “/” which will add the route to method "addUser". This means that, method “addUser” will be called when url will be - localhost:4000/user/.
  • In order to make this method visible - we are using decorator “Worker”. The parameter “HTTP_METHOD.Post” makes the method only work when the request method will be POST.
  • The method addUser - takes data from the body (post data) and adds the user to store by calling service. After the successful addition, it returns the added user with HTTP code - 201 (Resource Created).
In summary, we have created a method "addUser" that is used to add users. It only works for HTTP method post & route "/".You can test this by sending a post request to URL - "localhost:4000/user/" with user model value as the body of the request.
 
 
We have successfully created the POST endpoint. But one thing to note here is that we are not doing any validations for the user. It might be that invalid data is supplied in the post request.
 
We can write code inside “addUser” method to validate input data or write a separate method inside a controller (like validateUser) for validation but as I said in the introduction part - "fort.js provides components for modularization". Let's use components for validation,  Since we are doing operations on a worker, we need to use Guard component.
 

Guard

 
Create a folder “guards” and a file “ model_user_guard.js” inside the folder. Write the below code inside the file,
  1. import { Guard, HTTP_STATUS_CODE, textResult } from "fortjs";  
  2. import { User } from "../models/user";  
  3. import { isEmail, isLength, isIn } from "validator";   
  4.   
  5. export class ModelUserGuard extends Guard {  
  6.   
  7.     validate(user) {  
  8.         let errMessage;  
  9.         if (user.name == null || !isLength(user.name, 5)) {  
  10.             errMessage = "name should be minimum 5 characters"  
  11.         } else if (user.password == null || !isLength(user.password, 5)) {  
  12.             errMessage = "password should be minimum 5 characters";  
  13.         } else if (user.gender == null || !isIn(user.gender, ["male""female"])) {  
  14.             errMessage = "gender should be either male or female";  
  15.         } else if (user.gender == null || !isEmail(user.emailId)) {  
  16.             errMessage = "email not valid";  
  17.         } else if (user.address == null || !isLength(user.address, 10, 100)) {  
  18.             errMessage = "address length should be between 10 & 100";  
  19.         }  
  20.         return errMessage;  
  21.     }  
  22.   
  23.     async check() {  
  24.         const user = new User(this.body);  
  25.         const errMsg = this.validate(user);  
  26.         if (errMsg == null) {  
  27.             // pass user to worker method, so that they dont need to parse again  
  28.             this.data.user = user;  
  29.             // returning null means - this guard allows request to pass  
  30.             return null;  
  31.         } else {  
  32.             return textResult(errMsg, HTTP_STATUS_CODE.BadRequest);  
  33.         }  
  34.     }  
  35. }  
In the above code,
  • We are writing code inside the check method, which is part of guard lifecycle. We are validating the user inside it.
  • If the user is valid, then we are passing the user by using "data" property and returning null. Returning null means guard has allowed this request and the worker should be called.
  • If a user is not valid, we are returning an error message as text response with HTTP code- "badrequest". We are returning textResult, which means the fort.js will consider this as response and worker won't be called.
Now we need to add this guard to method “addUser”. 
  1. @Guards([ModelUserGuard])    
  2. @Worker([HTTP_METHOD.Post])    
  3. @Route("/")    
  4. async addUser() {    
  5.     const user: User = this.data.user;    
  6.     const service = new UserService();    
  7.     return jsonResult(service.addUser(user), HTTP_STATUS_CODE.Created);    
  8. }   
In the above code,
  • I have added the guard, “ModelUserGuard” using the decorator Guards .
  • With the guard in the process, we don't need to parse the data from body anymore inside worker, we are reading it from this.data which we are passing from "ModelUserGuard".
  • The method “addUser” will be only called when Guard allow means if all data is valid.
You can see that our worker method looks very light after using component.
 

PUT

 
Now we need to create a method, which will update the user and will only work for HTTP method — “PUT”.
 
Let’s add another method - “updateUser” with route “/” , guard — “ModelUserGuard” (for validation of user) and most important - worker with http method — “PUT”
  1. @Worker([HTTP_METHOD.Put])    
  2. @Guards([ModelUserGuard])    
  3. @Route("/")    
  4. async updateUser() {    
  5.       const user: User = this.data.user;    
  6.       const service = new UserService();    
  7.       const userUpdated = service.updateUser(user);    
  8.       if (userUpdated === true) {    
  9.           return textResult("user updated");    
  10.       }    
  11.       else {    
  12.           return textResult("invalid user");    
  13.       }    
  14. }    
The above code is very simple. It is just calling the service code to update the user. But one important thing to notice is that we have reutilized the guard - “ModelUserGuard” and it makes our code very clean.
 
So we are done with,
  • GET - Returns all users
  • POST - add users
  • PUT - update user
Currently, the GET request returns all the users but what if we want to get only one user.
 
Let’s see how to do it.
 
We have created a method “getUsers” for returning all users. Now let’s create another method “getUser”, which will return only one user.
  1. @Worker([HTTP_METHOD.Get])    
  2. @Route("/{id}")    
  3. async getUser() {    
  4.       const userId = Number(this.param.id);    
  5.       const service = new UserService();    
  6.       const user = service.getUser(userId);    
  7.       if (user == null) {    
  8.           return textResult("invalid id");    
  9.       }    
  10.       return jsonResult(user);    
  11. }   
In the above code, we are using a placeholder in route. Now “getUser” will be called when url will be something like localhost:4000/user/1 The placeholder value is being consumed by using "this.param" .
 

REMOVE

 
We will use the same concept as get.
  1. @Worker([HTTP_METHOD.Delete])    
  2. @Route("/{id}")    
  3. async removeUser() {    
  4.       const userId = Number(this.param.id);    
  5.       const service = new UserService();    
  6.       const user = service.getUser(userId);    
  7.       if (user != null) {    
  8.           service.removeUser(userId);    
  9.           return textResult("user deleted");    
  10.       }    
  11.       else {    
  12.           return textResult("invalid user");    
  13.       }    
  14. }    
In the above code, we are just calling the service to remove the user after getting the id from route.
 
Finally, we have successfully created a REST endpoint for the user.
 
References
  • http://fortjs.info/
  • https://medium.com/fortjs