In this article, we will use fortjs - a nodejs MVC framework that is fully compatible with TypeScript and next gen JavaScript - es6, es7.
SetUp
Clone or download the TypeScript starter project of fortjs here.
After you have downloaded the project. Open the Console and move to project directory and do the following steps,
Open the URL - localhost:4000 in browser. You will see something like below.
**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 to REST,
For creating an endpoint, we need to create a Controller. You can read about controller here.
Create a file user_controller.ts inside the contollers 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 controller but it’s still unknown by fortjs. In order to use this controller, we need to add this to routes. Open routes.ts inside 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, 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 default method inside “UserController”.
Note: One thing to notice here is that the code looks very simple and nice. This is possible due to TypeScript and fortjs. And another fun is that you will get IntelliSense support and this all make life easy for a developer :).
Service
Before moving further, let’s write service code, which will help us to do CRUD operations.
Create a folder “models” and then a file “user.ts” inside the folder. Paste the below code inside the file.
import { Length, Contains, IsIn, IsEmail } from "class-validator";
export class User {
id?: number;
@Length(5)
password?: string;
@Length(5)
name: string;
@IsIn(["male", "female"])
gender: string;
@Length(10, 100)
address: string;
@IsEmail()
emailId: string;
constructor(user: any) {
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;
}
}
I am using a npm plugin - “class-validator” to validate the model. This model “user” will be used by service and controller for transfer of data.
Create a folder “services” and then a file “ user_service.ts” inside the folder. Paste the below code inside the file.
import { User } from "../models/user";
interface IStore {
users: User[];
}
const store: IStore = {
users: [{
id: 1,
name: "durgesh",
address: "Bengaluru india",
emailId: "durgesh@imail.com",
gender: "male",
password: "admin"
}]
}
export class UserService {
getUsers() {
return store.users;
}
addUser(user: 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: 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: number) {
return store.users.find(user => user.id === id);
}
removeUser(id: number) {
const index = store.users.findIndex(user => user.id === id);
store.users.splice(index, 1);
}
}
Above code contains a variable store, which contains collection of users and the method inside the service do operation like — add, update, delete, get on that store. Basically i have made a fake db and doing some crud operation.
GET
We are going to create an endpoint for getting user.
Let’s rename the default methods to “getUsers” which will return all users. Replace the code inside user_controller.ts by below code.
import { Controller, jsonResult, DefaultWorker} from 'fortjs'
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 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:
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,
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 body of the request.
So 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 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.
Let’s add the validation code.
import { Controller, jsonResult, DefaultWorker, HTTP_METHOD, HTTP_STATUS_CODE, Worker, Route } from 'fortjs'
import { User } from '../models/user';
import { validate } from "class-validator";
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
}
as User;
const errorMsg = await this.validateUser(user);
if (errorMsg == null) {
const service = new UserService();
const newUser = service.addUser(user);
return jsonResult(newUser, HTTP_STATUS_CODE.Created);
} else {
return textResult(errMessage, HTTP_STATUS_CODE.BadRequest);
}
}
async validateUser(user: User) {
const errors = await validate('User', user);
if (errors.length === 0) {
return null;
} else {
const error = errors[0];
const constraint = Object.keys(error.constraints)[0];
const errMessage = error.constraints[constraint];
return errMessage;
}
}
}
Ok, so we have added the code for data validation. It will work as expected but dont you think the code looks little bit messy?
FortJs provides components for validation and any extra work, so that your code looks much cleaner and easy to manage.
FortJs says - “A worker should only have code related to its main purpose and extra code should be written into components.”
Let’s implement the above validation using components. Since we are doing operations on worker, we need to use Guard component.
Guard
Create a folder “guards” and a file “ model_user_guard.ts” inside the folder. Write the below code inside the file.
import { Guard, HttpResult, MIME_TYPE, HTTP_STATUS_CODE, textResult } from "fortjs";
import { User } from "../models/user";
import { validate } from "class-validator";
export class ModelUserGuard extends Guard {
async check() {
const user: User = new User(this.body);
// here i am using a plugin to validate but you can write your own code too.
const errors = await validate('User', user);
if (errors.length === 0) { // allow the request to pass through guard
// pass this to method, so that they dont need to parse again
this.data.user = user;
return null;
}
else { // block the request
const error = errors[0];
const constraint = Object.keys(error.constraints)[0];
const errMessage = error.constraints[constraint];
return textResult(errMessage, HTTP_STATUS_CODE.BadRequest);
}
}
}
In the above code,
Now we need to add this guard to 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,
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”
@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,
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 route.
Finally, we have successfully created a REST endpoint for user.
TypeScript makes JavaScript development very faster with static typing & IntelliSense support. On the other hand, fortjs helps write clean and modular server side code.
Thank you for reading!
#node-js #Rest API #typescript #fortjs