NodeJs Development with modern javascript using fortjs


Introduction

Nodejs gives you power to write server side code using JavaScript. In fact, it is very easy & fast to create a web server using Nodejs. There are several frameworks available on node package manager which makes the development even more easy & faster.

But there are few challenges in Nodejs development:

  • Node js is all about callbacks and with more & more callback, you end up with a situation called callback hell.
  • Writing readable code.
  • Writing maintainable code.
  • You don’t get much intellisense support which makes development slow.

If you are well experienced & have a good knowledge of nodejs, you can use different techniques & try to minimize those challenges.

The best way to solve these problems are by using modern JavaScript ES6, ES7 or typescript, whatever you feel comfortable with. I recommend typescript, because it provides intillisense support for every word of code which makes your development faster.

So i created a framework FortJs – which is very easy to learn & use. The FortJs enables you to write server-side code using es6 or typescript which is modular, secure, and pretty much beautiful & readable.

Features

Some of important features of fortjs are –  

  • Based on architecture Fort .
  • MVC Framework & follows OOPS approach so everything is class and object.
  • Provides component – Wall, Shield and Guard. Component helps to modularize the application.
  • Uses es6 async/await or promise for executing asychronous code.
  • Everything is configurable – you can configure your session store, view engine, websocket etc.
  • Dependency Injection.
  • Everything can be unit tested, so you can use TDD approach.

Let’s Code

In this article – I am going to create REST API using fortjs & es6. But you can use the same code & steps to implement using TypeScript too.

Project Setup

FortJs provides a CLI – fort-creator. This helps you to set up the project and develop faster. Let’s use the CLI to develop –

Perform the below steps sequentially:

  • Open terminal or command prompt.
  • Install the FortJs globally – run the command “npm i fort-creator -g”. Note:- Make sure you have nodejs installed in your system.
  • Create a new project – run command “fort-creator new my-app”. Here “my-app” is the name of the app, you can choose any. The CLI will prompt you to choose language with two options: typescript & javascript. Choose your language by using arrow keys and press enter. Since I’m going to use es6, I have chosen javascript. It will take some time to create the project, so please wait until you see “new project my-app created”.
  • Enter into the project directory – “cd my-app”.
    Start the development server with live reloading – run command “fort-creator start”.
  • Open the browser & type the URL – http://localhost:4000/.

You should see something like this in the browser.

fortjs starter page

Let’s understand how this page is rendered:

  • Open the project folder in your favourite code editor, I am going to use vscode. You will see many folders inside project root such as controllers, views, etc. Every folder is grouped by their use like controllers folder contains all controller & views folder contains all view.
  • Open controllers folder -> Inside the controllers, you will see a file name – default_controller, let’s open it and observe the code. The file contains a class DefaultController – this is a controller class and it contains methods which return some http response.
  • Inside the class DefaultController -> you will see a method ‘index’ – this is the one which is rendering current output to browser. The method is known as worker in fortjs because they do some kind of work & return result as http response. Let’s observe the index method code:

    “`
    const data = {
       title: title
    }
    const result = await viewResult(‘default/index.html’, data);
    return result;
    “`
    It creates a data object and passes that object into viewResult method. The viewResult method takes two parameter – the view location and view data. The work of viewResult is to render the view and return response which we are seeing in the browser.

  • Let’s find the view code and understand it. Open views folder – > open default folder – > open index.html. This is our view code. It is simple HTML code along with some mustache syntax. Fortjs default view engine is mustache.

Hope you have understood the project architecture but if you are having any difficulty or doubt, please feel free to ask in comment section.

Now we will move to next part of this article, where we will learn how to create a simple rest API.

REST

We are going to create a REST endpoint for entity user – which will perform the CRUD operations for 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 http method “GET
  4. Updating user – should be done using http method “PUT

For creating an endpoint, we need to create a Controller similar to default controller explained earlier.

Execute the command – “fort-creator add“. It will ask “Choose the component to add ?” Choose Controller & press enter. Enter controller name “User” & press enter.

Now we have created user controller, we need to inform the fortjs by adding it to routes. The route is used to map our controller to a path.

Since our entity is user , so “/user” will be a good route. Let’s add it – Open routes.js inside root directory of the project and add UserController to routes.

After adding UserController, routes.js will look like this:

import { DefaultController } from "./controllers/default_controller";
import { UserController } from "./controllers/user_controller";

export const routes = [{
    path: "/*",
    controller: DefaultController
},
{
    path: "/user",
    controller: UserController
}]
routes.js

So when an http request will have path “/user” then UserController will be called.

Let’s open the url – http://localhost:4000/user.

Note :- If you have stopped the fortjs while adding the controller. Please start it again by runing cmd – fort-creator start

And you see a white page right?

This is because – we are not returning anything from index method and thus blank response. Let’s return a text “Hello World” from index method. Add the below code inside index method & save:

return textResult('Hello World');

Refresh the url – http://localhost:4000/user

And you see “Hello World” right ?

Now, let’s convert “UserController” to a REST API. But before writing code for REST API, let’s create a dummy service which will do crud operation for users.

SERVICE

Create a folder “services” and then a file “user_service.js” inside the folder. Paste the below code inside the file

const store = {
    users: [{
        id: 1,
        name: "ujjwal",
        address: "Bangalore India",
        emailId: "[email protected]",
        gender: "male",
        password: "admin"
    }]
}

export class UserService {
    getUsers() {
        return store.users;
    }

    addUser(user) {
        const lastUser = store.users[store.users.length - 1];
        user.id = lastUser == null ? 1 : lastUser.id + 1;
        store.users.push(user);
        return user;
    }

    updateUser(user) {
        const existingUser = store.users.find(qry => qry.id === user.id);
        if (existingUser != null) {
            existingUser.name = user.name;
            existingUser.address = user.address;
            existingUser.gender = user.gender;
            existingUser.emailId = user.emailId;
            return true;
        }
        return false;
    }

    getUser(id) {
        return store.users.find(user => user.id === id);
    }

    removeUser(id) {
        const index = store.users.findIndex(user => user.id === id);
        store.users.splice(index, 1);
    }
}

The above code contains a variable store which contains a collection of users and the method inside the service does operations like add, update, delete, and get on that store.

We will use this service in REST API implementation.

GET

For route “/user” with http method “GET”, the API should return list of all users.

In order to implement this, let’s rename “index” method inside user_controller.js to “getUsers” making semantically correct & paste the below code inside the method:

const service = new UserService();
return jsonResult(service.getUsers());

now user_controller.js looks like this:


import { Controller, DefaultWorker, Worker, textResult, jsonResult } from "fortjs";
import { UserService } from "../services/user_service";

export class UserController extends Controller {

    @DefaultWorker()
    async getUsers() {
        const service = new UserService();
        return jsonResult(service.getUsers());
    }
}

Here, we are using decorator DefaultWorker. The DefaultWorker does two things – it adds the route “/” & http method “GET”. Its a shortcut for this scenario. In the next part, we will use other decorators to customize the route.

Let’s test this by calling url http://localhost:4000/user. You can open this in the browser or use any http client tools like postman, curl

Ok, so we have successfully created an end point 🙂 .

Let’s look again at our code & observe if we can make it better –

  1. The service “UserService” is tightly coupled with Controller “UserController” which becomes a problem for unit testing “UserController”. So we will use dependency injection by fortjs to inject UserService.
  2. We are creating an instance of “UserService” everytime when method getUsers is called but what we need from “UserService” is a single object and then call the “UserService” method from the object.

    So if we can somehow store an object of “UserService” then we can make our code more faster (because calling new does some work under the hood). For this we will use the singleton feature of fortjs.

Let’s change user_controller.js code by below code –


import { Controller, DefaultWorker, Worker, textResult, jsonResult, Singleton } from "fortjs";
import { UserService } from "../services/user_service";

export class UserController extends Controller {

    @DefaultWorker()
    async getUsers(@Singleton(UserService) service) {
        return jsonResult(service.getUsers());
    }
}

As you can see only change we have done is – using the “Singleton” decorator in method getUsers – which will create a singleton and inject that singleton when getUsers will be called. This singleton will be available throughout the application.

Since now service is a parameter, we can manually pass the parameter while calling, so this makes getUsers unit testable.

For doing unit testing or e2e testing, please read test doc – http://fortjs.info/tutorial/test/

POST

Let’s add a method “addUser” which will extract data from request body and call service to add user.

async addUser(@Singleton(UserService) service) {
        const user = {
            name: this.body.name,
            gender: this.body.gender,
            address: this.body.address,
            emailId: this.body.emailId,
            password: this.body.password
        };
        const newUser = service.addUser(user);
        return jsonResult(newUser, HTTP_STATUS_CODE.Created);
}

In the above code we are creating the Singleton of the UserService again. So question is will it create another object ?

No it will be same object what was in getUser and fortjs supplies the object as parameter when calls the method.

The methods created are by default not visible for http request. So in order to make this method visible for http request, we need to mark this as worker.

A method is marked as worker by adding decorator “Worker”. The Worker decorator takes list of http method & make that method available for only those http methods. So let’s add the decorator:

@Worker([HTTP_METHOD.Post])
async addUser(@Singleton(UserService) service) {
    const user = {
        name: this.body.name,
        gender: this.body.gender,
        address: this.body.address,
        emailId: this.body.emailId,
        password: this.body.password
    };
    const newUser = service.addUser(user);
    return jsonResult(newUser, HTTP_STATUS_CODE.Created);
}

Now route of this method is the same as name of the method that is “addUser”. You can check this by sending a post request to http://localhost:4000/user/addUser with user data in body.

But we want  route to be “/”, so that it will be a rest API. The route of worker is configured by using the decorator “Route”. Let’s change the route now.

@Worker([HTTP_METHOD.Post])
@Route("/")
async addUser(@Singleton(UserService) service) {
    const user = {
        name: this.body.name,
        gender: this.body.gender,
        address: this.body.address,
        emailId: this.body.emailId,
        password: this.body.password
    };
    const newUser = service.addUser(user);
    return jsonResult(newUser, HTTP_STATUS_CODE.Created);
}

Now our end point is configured for post request. Let’s test this by sending a post request to http://localhost:4000/user/ with user data in body.

It returns the user created with id which is our logic, so we have created the end point for post request but one important thing to do is validation of data. Validation is an essential part of any app & very essential for backend application.

So far, our code is clean & readable but if we will add validation code it will become little dirty.

Worry not, FortJs provides component Guard for this kind of work. A/c to fortjs doc:

Guard is security layer on top of Worker. It contols whether a request should be allowed to call the Worker.

So we are going to use guard for validation of data. Let’s create the guard using fort-creator. Execute command – fort-creator add & choose Guard. Enter the file name “UserValidator”. There will be a file “user_validator_guard.js” created inside guards folder, open that file.

A guard has access to the body, so you can validate data inside that. Returning null inside method check means allow to call worker & returning anything else means block the call.

Let’s make it more clear by writing code for validation. Paste the below code inside the file “user_validator_guard.js”:


import { Guard, textResult, HTTP_STATUS_CODE } from "fortjs";

export class UserValidatorGuard extends Guard {

    async check() {
        const user = {
            name: this.body.name,
            gender: this.body.gender,
            address: this.body.address,
            emailId: this.body.emailId,
            password: this.body.password
        };
        const errMsg = this.validate(user);
        if (errMsg == null) {
            // pass user to worker method, so that they dont need to parse again  
            this.data.user = user;
            // returning null means - guard allows request to pass  
            return null;
        } else {
            return textResult(errMsg, HTTP_STATUS_CODE.BadRequest);
        }
    }
    
    validate(user) {
        let errMessage;
        if (user.name == null || user.name.length < 5) {
            errMessage = "name should be minimum 5 characters"
        } else if (user.password == null || user.password.length < 5) {
            errMessage = "password should be minimum 5 characters";
        } else if (user.gender == null || ["male", "female"].indexOf(user.gender) < 0) {
            errMessage = "gender should be either male or female";
        } else if (user.emailId == null || !this.isValidEmail(user.emailId)) {
            errMessage = "email not valid";
        } else if (user.address == null || user.address.length < 10) {
            errMessage = "address length should be greater than 10";
        }
        return errMessage;
    }
    
    isValidEmail(email) {
        var re = /^(([^<>()[].,;:[email protected]"]+(.[^<>()[].,;:[email protected]"]+)*)|(".+"))@(([[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}])|(([a-zA-Z-0-9]+.)+[a-zA-Z]{2,}))$/;
        return re.test(String(email).toLowerCase());
    }


}

In the above code:

  • We have created a method validate which takes parameter user. It validates the user & returns the error message if there is validation error, otherwise null.
  • We are validating data inside the check method, which is part of guard lifecycle. We are validating the user inside it by calling method validate.
    If the user is valid, then we are passing the user value 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- “Bad Request”. In this case, execution will stop here & worker won’t be called.

In order to activate this guard for method addUser, we need to add this on top of addUser. The guard is added by using decorator “Guards”. So let’s add the guard:

@Worker([HTTP_METHOD.Post])
@Route("/")
@Guards([UserValidatorGuard])
async addUser(@Singleton(UserService) service) {
    const newUser = service.addUser(this.data.user);
    return jsonResult(newUser, HTTP_STATUS_CODE.Created);
}

In the above code:

  • I have added the guard, “UserValidatorGuard” 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 “UserValidatorGuard”.
  • The method “addUser” will be only called when Guard allow means if all data is valid.

One thing to note is that – method “addUser” looks very light after using component & it’s doing validation too. You can add multiple guard to a worker which gives you the ability to modularize your code into multiple guards & use that guard at multiple place.

Isn’t this cool :D?

Let’s try adding user with some invalid data:

As you can see in the picture, I have tried with sending request without password & the result is – “password should be minimum 5 characters”. So it means guard is activated & working perfectly.

PUT

Let’s add another method – “updateUser” with route “/” , guard — “UserValidatorGuard” (for validation of user) and most important – worker with http method “PUT”.

@Worker([HTTP_METHOD.Put])
@Guards([UserValidatorGuard])
@Route("/")
async updateUser(@Singleton(UserService) service) {
    const user = this.data.user;
    const userUpdated = service.updateUser(user);
    if (userUpdated === true) {
        return textResult("user updated");
    } else {
        return textResult("invalid user");
    }
}

Update code is similar to addUser code except functionality wise that is updating of data. Here, we have reutilized UserValidatorGuard to validate data.

DELETE

In order to delete data, user needs to pass id of the user. This can be passed by using three ways:

  • Sending data in body just like we did for add & update – {id:1}
  • Sending data in query string – ?id=1
  • Sending data in route – for this, we need to customize our route – “/user/1”

We have already implemented getting data from body. So let’s see other two ways:

Sending Data in Query String

Let’s create method “removeByQueryString” and paste the below code:

@Worker([HTTP_METHOD.Delete])
@Route("/")
async removeByQueryString(@Singleton(UserService) service) {
    // taking id from query string
    const userId = Number(this.query.id);
    const user = service.getUser(userId);
    if (user != null) {
        service.removeUser(userId);
        return textResult("user deleted");
    } else {
        return textResult("invalid user", 404);
    }
}

Sending Data in Route

You can parameterise the route by using “{var}” in a route. Let’s see how?

Let’s create another method “removeByRoute” and paste the below code:

@Worker([HTTP_METHOD.Delete])
@Route("/{id}")
async removeByRoute(@Singleton(UserService) service) {
    
    // taking id from route
    const userId = Number(this.param.id);

    const user = service.getUser(userId);
    if (user != null) {
        service.removeUser(userId);
        return textResult("user deleted");
    } else {
        return textResult("invalid user");
    }
}

The above code is exactly the same as removeByQueryString except that it is extracting the id from route & using parameter in route i.e., “/{id}” where id is parameter.

Let’s test this:

So we have finally created REST API for all the funtionalities except GET particular user by id. I will leave that to you for exercise.

POINTS OF INTEREST

Q. How to add authentication to “UserController”, so that any unauthenticated request can’t call the “/user” end point.

Ans :- There are multiple approach for this –

REFERENCES



Source link