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 a 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 future-proof apps.
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 that 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. 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.
- Wall: This top-level level component & every HTTP request is passed through the wall first.
- Shield: This is available for controllers. You can assign a shield to the controller & it will be executed before a controller is called.
- 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 the entity user - which will perform the CRUD operations for the user such as adding the user, deleting the user, getting the user, and updating the user.
According to REST
- Adding user: Should be done using the HTTP method "POST"
- Deleting user: Should be done using the HTTP method "REMOVE"
- Getting user: Should be done using the HTTP method "GET"
- Updating user: Should be done using the HTTP method "PUT"
To create 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
import { Controller, textResult, DefaultWorker } from 'fortjs'
export class UserController extends Controller {
@DefaultWorker()
async default() {
return textResult('you have successfully created a user controller');
}
}
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 results by using the method textResult from fort.js. textResult returns an 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 an HTTP request (no worker means it is just a function that is only available for this class). A default worker is a worker which adds the route "/" for the target method. Please take a look at the worker doc - http://fortjs.info/tutorial/worker/
We have created a controller but it's still unknown by fort.js. To use this controller, we need to add this to routes. Open routes.js inside the root folder and add UserController to routes.
import { DefaultController } from "./controllers/default_controller"
import { UserController } from "./controllers/user_controller"
export const routes = [
{
path: "/default",
controller: DefaultController
},
{
path: "/user",
controller: UserController
}
]
You can see, that 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 that 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.
export class User {
constructor(user) {
this.id = Number(user.id)
this.name = user.name
this.gender = user.gender
this.address = user.address
this.emailId = user.emailId
this.password = user.password
}
}
Create a folder “services” and then a file “ user_service.js” inside the folder. Paste the below code inside the file.
import { User } from "../models/user"
const store = {
users: [
{
id: 1,
name: "durgesh",
address: "Bengaluru 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.
So now we have a service, we need to write the code to use those services 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 with the below code.
import { Controller, DefaultWorker, jsonResult } from 'fortjs'
import { UserService } from '../service/user_service'
export class UserController extends Controller {
@DefaultWorker()
async getUsers() {
const service = new UserService()
return jsonResult(service.getUsers())
}
}
As you can see, we are using DefaultWorker since it makes the method visible for HTTP requests and adds route "/" with the HTTP method "GET". So all these things use 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 the HTTP method "POST". So now, “UserController” looks like this
import { Controller, jsonResult, DefaultWorker, HTTP_METHOD, HTTP_STATUS_CODE, Worker, Route } from 'fortjs'
export class UserController extends Controller {
@DefaultWorker()
async getUsers() {
const service = new UserService()
return jsonResult(service.getUsers())
}
@Worker([HTTP_METHOD.Post])
@Route("/")
async addUser() {
const user = {
name: this.body.name,
gender: this.body.gender,
address: this.body.address,
emailId: this.body.emailId,
password: this.body.password
}
const service = new UserService()
const newUser = service.addUser(user)
return jsonResult(newUser, HTTP_STATUS_CODE.Created)
}
}
In the above code
- We have created a method "addUser" and added a decorator “Route” with parameter “/” which will add the route to the method "addUser". This means that the method “addUser” will be called when the URL will be - localhost:4000/user/.
- To make this method visible: We are using the 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 the store by calling the 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 the URL - "localhost:4000/user/" with the 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 the “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
import { Guard, HTTP_STATUS_CODE, textResult } from "fortjs"
import { User } from "../models/user"
import { isEmail, isLength, isIn } from "validator"
export class ModelUserGuard extends Guard {
validate(user) {
let errMessage
if (user.name == null || !isLength(user.name, 5)) {
errMessage = "Name should be minimum 5 characters"
} else if (user.password == null || !isLength(user.password, 5)) {
errMessage = "Password should be minimum 5 characters"
} else if (user.gender == null || !isIn(user.gender, ["male", "female"])) {
errMessage = "Gender should be either male or female"
} else if (user.gender == null || !isEmail(user.emailId)) {
errMessage = "Email not valid"
} else if (user.address == null || !isLength(user.address, 10, 100)) {
errMessage = "Address length should be between 10 & 100"
}
return errMessage
}
async check() {
const user = new User(this.body)
const errMsg = this.validate(user)
if (errMsg == null) {
// Pass user to worker method, so that they don't need to parse again
this.data.user = user
// Returning null means - this guard allows request to pass
return null
} else {
return textResult(errMsg, HTTP_STATUS_CODE.BadRequest)
}
}
}
In the above code
- We are writing code inside the check method, which is part of the guard lifecycle. We are validating the user inside it.
- If the user is valid, then we are passing the user by using the "data" property and returning null. Returning null means the guard has allowed this request and the worker should be called.
- If a user is not valid, we are returning an error message as a text response with the HTTP code- "bad request". We are returning textResult, which means the fort.js will consider this as a response and the worker won't be called.
Now we need to add this guard to the method “addUser”
@Guards([ModelUserGuard])
@Worker([HTTP_METHOD.Post])
@Route("/")
async addUser() {
const user: User = this.data.user
const service = new UserService()
return jsonResult(service.addUser(user), HTTP_STATUS_CODE.Created)
}
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 the body anymore inside the worker, we are reading it from this. data which we are passing from "ModelUserGuard".
- The method “addUser” will be only called when Guard allows means if all data is valid.
You can see that our worker method looks very light after using the component.
Put
Now we need to create a method, which will update the user and will only work for the HTTP method — “PUT”.
Let’s add another method - “update user” with route “/”, guard — “ModelUserGuard” (for validation of user), and most important - worker with HTTP method — “PUT”
@Worker([HTTP_METHOD.Put])
@Guards([ModelUserGuard])
@Route("/")
async updateUser() {
const user: User = this.data.user
const service = new UserService()
const userUpdated = service.updateUser(user)
if (userUpdated === true) {
return textResult("User updated")
} else {
return textResult("Invalid user")
}
}
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.
@Worker([HTTP_METHOD.Get])
@Route("/{id}")
async getUser() {
const userId = Number(this.param.id)
const service = new UserService()
const user = service.getUser(userId)
if (user == null) {
return textResult("Invalid id")
}
return jsonResult(user)
}
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.
@Worker([HTTP_METHOD.Delete])
@Route("/{id}")
async removeUser() {
const userId = Number(this.param.id)
const service = new UserService()
const user = service.getUser(userId)
if (user != null) {
service.removeUser(userId)
return textResult("User deleted")
} else {
return textResult("Invalid user")
}
}
In the above code, we are just calling the service to remove the user after getting the ID from the route.
Finally, we have successfully created a REST endpoint for the user.
References
- http://fortjs.info/
- https://medium.com/fortjs