2.0.1 • Published 2 years ago

pretty-express v2.0.1

Weekly downloads
-
License
ISC
Repository
-
Last release
2 years ago

Pretty-Express

Typescript Decorators for writing pretty express code

What is it

Pretty express is a library that adds Typescript decorators to methods/classes and parameters to simplify the way we write express code. It also includes jwt authenticaion and validation decorator built using class-validator

Features

  • Controllers with a base route, marked with @Controller decorator
  • @get @post @patch @del @put and @all decorators available for methods
  • @requestBody @requestParams @authUser and @requestQuery as parameter decorators inside decorated methods
  • @validate decorator for request body validation
  • @authenticate decorator for jwt authentication with roles
  • @middleware decorator passing middlewares

Installation

$ npm install --save pretty-express
$ npm install --save reflect-meta

IMPORTANT requires experimentalDecorators & emitDecoratorMetadata set to true in the tsconfig.json file. (check this for reference )

also requires import "reflect-metadata";' at the top of your entry level file (eg index.ts)

Getting started

Creating your controller :

import "reflect-metadata";
import { Controller, get, post, requestParams, Server } from "pretty-express";
import express, { Request, Response, NextFunction, Express } from "express";


@Controller("/api/users")
class UserController {
  @get("/")
  async getUsers(req: Request, res: Response, next: NextFunction) {
    return { message: "hello pretty express" }; 
    //the returned object is sent as json response with default status code : 200.
  }
// @requestParams passes the parameter data.
  @post("/:id")
  async addUsers(@requestParams data: any) {
    return { msg: "Recieved params ", data };
  }
}

Register your controller with your express app

class ApplicationServer extends Server {
  constructor(private app: Express) {
    super(app);
  }

  start() {
// register your controller
    this.addControllersToServer([new UserController()]);

    const port = process.env.PORT || 3000;
    this.app.listen(port, () => {
      console.log(`Listening on http://localhost:${port}`);
    });
  }
}

const app = express();
const server = new ApplicationServer(app);
server.start();

Parameter Decorators

Pretty express provides 3 parameter decorators for our decorated functions

decoratorsdetails
@requestBodythe request body
@requestParamsthe request parameters
@authUserthe credentials of the authenticated user eg {id , iat}
@requestQuerythe request query object
  @post("/")
  async addUsers(@requestBody data: UserCredentials , @requestParams params : any , @authUser authUser : any) {
    return  {data};
  }

if no parameters are decorated the default arguments (Request, Response, NextFunction) from the express RouterHandler will be passed

  @get("/")
  async getUsers(req: Request, res: Response, next: NextFunction) {
    return { message: "hello pretty express" };
  }

Optionally You can also access the express routeHandler params as the final 3 arguments of the function in order.

  @get("/")
  async getUsers(@requestBody data : any, req: Request, res: Response, next: NextFunction) {
    return { message: "hello pretty express" };
  }

returning a response

By default the returned object of a decorated fucntion is returned as a JSON object with a status : 200

  @del("/")
  async someFunction(req: Request, res: Response, next: NextFunction) {
    return { method: "delete" };
  }

To return a response with a custom status code. Simply return an object of type HttpResponse(status, object)

  @post("/")
  async someFunction(req: Request, res: Response, next: NextFunction) {
    return new HttpResponse(201, { message: "created" });
  }

to return an error response, throw an error of type HttpErrorResponse

  @patch("/")
  async someFunction(req: Request, res: Response, next: NextFunction) {
    throw new HttpErrorResponse(400, "Hello Error!");
  }

You can also automatically get the status codes from static methods provided in HttpErrorResponse and HttpResponse.

@post("/user")
  async someFunction() {
    throw HttpErrorResponse.NOT_IMPLEMENTED("This method is not implemented!");
  }

Similary for HttpResponse

@post("/")
  async addUser(req: Request, res: Response, next: NextFunction) {
    return HttpResponse.CREATED({data : "new data entered"})
  }

Request Level and Controller level middlewares

You can pass middlewares using the @middleware decorator on controllers and functions

define your middleware

function requestMiddleware (req : Request,res : Response,next : NextFunction) {
  console.log("Entered request middleware. Do something")
  next()
}

use the @middleware decorator

  @middleware(requestMiddleware)
  @post("/:id")
  async addUsers(@requestParams data: any) {
    return { msg: "Recieved params ", data };
  }

you can also pass multiple middlewares. They will be executed in order of their indexes.

  @middleware(requestMiddleware , requestMiddleware2, requestMiddlewar3)
  @post("/:id")
  async addUsers(@requestParams data: any) {
    return { msg: "Recieved params ", data };
  }

Similarly on controllers

@middleware(controllerMiddleware)
@Controller("/api/users")
class UserController {
  @get("/")
  async getUsers(req: Request, res: Response, next: NextFunction) {
    return { message: "hello pretty express" };
  }

  // @requestParams passes the parameter data.
  @middleware(requestMiddleware)
  @post("/:id")
  async addUsers(@requestParams data: any) {
    return { msg: "Recieved params ", data };
  }
}

Note That the controller level middlewares are called first followed by the request level middlewares.

Request Body Validation

Request body is validated using the class-validator library Our library provides a @validate decorator were you can pass models created with class validator and validator options

Use npm install class-validator to get the model decorators used below. See class-validator page for more details about the available decorators

export class UserCredentials {
    @IsEmail()
    email: string;
  
    @IsString()
    password: string;
  
    @IsOptional()
    @IsString()
    name: string;
  }

@Controller("/api/users")
class UserController {
  // @requestBody passes the body data.
  @validate(UserCredentials, { whitelist: true })
  @post("/")
  async addUsers(@requestBody data: UserCredentials) {
    return  {data};
  }
}

The request body after validation is passed with the @requestBody decorator to our function where you can now access it.

It is also possible to use @validate at the controller level

@validate(UserCredentials)
@Controller("/api/users")
class UserController {
  @get("/")
  async getUsers(@requestBody data : any, req: Request, res: Response, next: NextFunction) {
    return { message: "hello pretty express" };
  }


  @post("/")
  async addUsers(@requestBody data: UserCredentials , @requestParams params : any , @authUser authUser : any) {
    return  {data};
  }
}

Transform Response body

The response body returned from the controller functions can be transformed using @transformResponse decorator, which uses the class-transformer package to transform the response bodies.

The main purpose of this decorator is to allow only selected keys or basic transformation of the json data.

@Exclude()
class DemoResponseSchema {

  @Expose()
  email: string;

  @Expose()
  name: string;

  @Expose()
  address?: string;

}


@transformResponse(DemoResponseSchema)
  @post("/transform")
  async transformResponseBody(@requestBody data: any) {
    const user = {
      name: "tenx",
      email: "tenx@gmail.com",
      invalid : "Some invalid data here"
    };
    return HttpResponse.CREATED(user);
  }

/*

returned response
{
  "name" : "tenx",
  "email" : "tenx@gmail.com"
 }
*/ 

JWT AUTHENTICATION (Testing / Under Development)

Pretty express provides @authenticate decorator to protect your routes The actual authenticaion logic is not implemented by the package. It only provides helpers and decorators to achieve the authentication.

To start, we need to create a AuthenticaionService class that inhereits JwtAuthenticationStrategy with a @AuthStrategy decorator

@AuthStrategy will take a string as an input which will be the name of our authentication strategy

In the service class we have to implement the given abstract methods:

functiondetails
generateTokenreturns a string token
verifyTokenverifies the token string and returns the decoded data
verifyCredentialschecks the data and the required role.

In the example below we used jsonwebtoken to generate the tokens.

import { JwtAuthenticationStrategy, AuthStrategy } from "pretty-express";
import jwt from "jsonwebtoken";

const jwtKey = "Auth Key";

export interface UserCredentials {
  id: string;
  email: string;
  role?: string;
}

@AuthStrategy("jwt")
export class MyJwtAuthService extends JwtAuthenticationStrategy {

  // generates a token
  async generateToken(credentials: UserCredentials): Promise<string> {
    return await jwt.sign(credentials, jwtKey);
  }

  // verifies the token and returns the decoded data
  async verifyToken(token: string): Promise<Object> {
    try {
      const decoded = await jwt.verify(token, jwtKey);
      return decoded;
    } catch (err) {
      throw new Error("An error occured while verifying token");
    }
  }

// verifies the role and other data. the requiredRole is recieved as a string array
  async verifyCredentials(
    credentials: UserCredentials,
    requiredRole?: string[]
  ): Promise<Object> {
    try {
      if (requiredRole && requiredRole.length > 0) {
        if (requiredRole.includes(credentials.role)) {
          throw new Error("User is not of required role. Access Denied!");
        }
      }
      return credentials;
    } catch (err) {
      throw err;
    }
  }
}

Register your service on your server

export class ApplicationServer extends Server {
  private app : Express;

  constructor(app : Express) {
    super(app);
    this.app = app;
  }

  start() {
// register authentication service here
    this.addAuthenticationStrategies([new MyJwtAuthService()]);
// register your controllers here
    this.addControllersToServer([new UserController()]);

    const port = process.env.PORT || 3000;
    this.app.listen(port, () => {
      console.log(`Listening on http://localhost:${port}`);
    });
  }
}

you can now use this authentication with the @authenticate decorator on your functions and controlllers

Request level

  @authenticate("jwt" , {role : ["admin"]})
  @post("/")
  async addUsers(@requestBody data: UserCredentials , @requestParams params : any , @authUser authUser : any) {
    return  {data};
  }

Controller level

@authenticate("jwt" , {role : ["user"]})
@Controller("/api/users")
class UserController {
  @get("/")
  async getUsers(@requestBody data : any, req: Request, res: Response, next: NextFunction) {
    return { message: "hello pretty express" };
  }

@authenticate("jwt" , {role : "admin"}) @post("/") async addUsers(@requestBody data: UserCredentials , @requestParams params : any , @authUser authUser : any) { return {data}; } }

> The authenticated User Data, data extracted from the token can be accessed with `@authUser` decorator on the function parameter.



> if no role is provided, it will skip the role check

````typescript
  @authenticate("jwt")
  @post("/")
  async addUsers(@requestBody data: UserCredentials , @requestParams params : any , @authUser authUser : any) {
    return  {data};
  }

CONCLUSION

Pretty Express is still under rapid development. To Contribute checkout the github page.

If you find any bugs please raise an issue on our github page.

THANKS!

2.0.1

2 years ago

2.0.0

2 years ago

1.1.0

2 years ago

1.0.34

2 years ago

1.0.33

2 years ago

1.0.32

2 years ago

1.0.31

2 years ago

1.0.30

2 years ago

1.0.29

2 years ago

1.0.28

2 years ago

1.0.27

2 years ago

1.0.26

2 years ago

1.0.25

2 years ago

1.0.24

2 years ago

1.0.23

2 years ago

1.0.22

2 years ago

1.0.21

2 years ago

1.0.20

2 years ago

1.0.19

2 years ago

1.0.18

2 years ago

1.0.17

2 years ago

1.0.16

2 years ago

1.0.15

2 years ago

1.0.14

2 years ago

1.0.13

2 years ago

1.0.12

2 years ago

1.0.10

2 years ago

1.0.9

2 years ago

1.0.8

2 years ago

1.0.7

2 years ago

1.0.6

2 years ago

1.0.5

2 years ago

1.0.4

2 years ago

1.0.3

2 years ago

1.0.2

2 years ago

1.0.1

2 years ago

1.0.0

2 years ago