0.2.2 • Published 1 year ago

bonfire-rest v0.2.2

Weekly downloads
-
License
ISC
Repository
github
Last release
1 year ago

header

bonfire-rest

A REST framework for building backend applications in Node. It is lightweight, easy to learn.

Built on express and type-chef-di

Installation

tsconfig.json

{
  "emitDecoratorMetadata": true,
  "experimentalDecorators": true
}

table of contents

Table of contents generated with markdown-toc

npm install bonfire-rest

Example of usage

  1. controller class
import { Controller, Param, Body, Get, Post, Put, Delete } from 'bonfire-rest';

@Controller()
export class UserController {
  @Get('/users')
  getAll() {
    return 'This action returns all users';
  }

  @Get('/users/:id')
  getOne(@Param('id') id: number) {
    return 'This action returns user #' + id;
  }

  @Post('/users')
  post(@Body() user: any) {
    return 'Saving user...';
  }

  @Put('/users/:id')
  put(@Param('id') id: number, @Body() user: any) {
    return 'Updating a user...';
  }

  @Delete('/users/:id')
  remove(@Param('id') id: number) {
    return 'Removing user...';
  }
}

This class will register routes specified in method decorators.

  1. Create a file app.ts
import { ServerBuilder } from "bonfire-rest";

async function main() {
  const port = Env.asNumber("PORT", 3000); // "Env" converts environment variables to differetnt types (envName, defaultValue)


  const app = express()
  const server = await ServerBuilder.build({ // setup and retun an express server
    controllers: [UserController],
    globalPipes: [ValidationPipe], // ValidationPipe will validate the request Body
    server: app, // optional, if no server provided it will create one
    globalMiddlewares: [LogMiddleware], // use thies middlewares before all actions
    openapi: { // openapi documentation, swagger ui
      spec: {info: {title: "test project", version: "1", description: "this is the test project decription"}, openapi: "3.0.0"}, // additional general informations
      swaggerUi: "/", // specify swagger ui route
      apiDocs: "docs" // specify openapi raw json route
    },
    assetFolders: [{root: "/assets", path: path.join(__dirname, "static")}] // static serve folders
  });

  server.listen(port, () => {
    console.log(`⚡️[server]: Server is running at http://localhost:${port}`);
  });
}

main();

Prefix routes:

  • Prefix all controllers routes: If you want to prefix all your routes, e.g. /api you can use globalPrefix: "api" option

  • Prefix controller with base route: You can prefix all specific controller's actions with base route:

@Controller("/users")
export class UserController {
  // ...
}

Inject endpoint parameters

Inject route parameters

You can use @Param("...") decorator to inject parameters in your controller actions:

@Get("/users/:id")
getOne(@Param("id") id: string) { 
}

If you want to inject all parameters use @Params().

Inject query parameters

To inject all query parameters, use @Query() decorator

To inject specific query parameter, use @QueryParam("...") decorator:

@Get("/users")
getUsers(@QueryParam("limit") limit: number, @Query() allQueryParam: any) {
}

Inject request body

To inject request body, use @Body decorator:

@Post("/users")
saveUser(@Body() user: User) {
}

To inject request body param, use @BodyParam("...") decorator

@Post("/users")
saveUser(@Body() user: User, @BodyParam("name") name: string) {
}

Inject request header parameters

To inject request header parameter, use @Header("...") decorator. To inject all request header parameter, use @Headers() decorator.

@Post("/users")
saveUser(@Header("authorization") token: string) {
}
@Post("/users")
saveUser(@Headers() allHeader: any) {
}

Inject request

@Post("/users")
saveUser(@Req() req: Request) {
}

Inject response

@Post("/users")
saveUser(@Res() res: Response) {
}

Pipes

Pipes can modify the value e.g. @Param, @Header, @Query.. You can chain them.

export class UpperCasePipe implements IPipe<string> {
  pipe(value: string): any {
    return value?.toUpperCase();
  }
}

@Controller( "ddd")
export class UserController {
    constructor(private readonly foo: FooService) {}

    @Get('/test')
    async getUsers(
      @Req() req: Request,
      @Res() res: Response,
      @QueryParam('name', [UpperCasePipe]) query: any,
    ) {
       return query // if query name got John_Wick the pipe will transformed to JOHN_WICK
    }
    ...

Middlewares

you can specify action middlewares with @BeforeMiddleware and @AfterMiddleware

  • @BeforeMiddleware runs before action
  • @AfterMiddleware runs after action
@Injectable()
export class LogMiddleware1 implements IMiddleware {
  constructor(private readonly stringFactory: StringFactory) { // you can use the DI
  }
  handle(req: express.Request, res: express.Response, next: Function) {
    console.log(`${LogMiddleware2.name} : before middleware`)
    next()
  }
}


@Injectable()
export class LogMiddleware2 implements IMiddleware {
  constructor(private readonly stringFactory: StringFactory) { // you can use the DI
  }
  handle(req: express.Request, res: express.Response, next: Function) {
    console.log(`${LogMiddleware3.name} : after middleware`)
    next()
  }
}

// ...
    @BeforeMiddleware(LogMiddleware1)
    @AfterMiddleware(LogMiddleware2, LogMiddleware2) // use as many you want, can be new instance
    @Get('/users')
    getTest(){
        return {user: "test"}
    }

Validation

for the request validation you can use class-validator

import {IsEmail, IsString} from "bonfire-rest";
import {IUser} from "../interfaces/user.interface";

export class UserCreateDto implements IUser {
  @IsString()
  username: string

  @IsString()
  password: string

  @IsEmail()
  email: string;

  @IsString()
  password2:string
}


@Controller("users")
export class UsersController {
  constructor(private readonly userService: UserService) {
  }

  @Get()
  get(){
    return UserModel.find({})
  }

  @Post()
  create(@Body() user: UserCreateDto) {
    if (user.password !== user.password2){
      throw new Error("Password is not the same.")
    }
    return this.userService.create(user);
  }


}

If the request body does not match with the class validation class it will throw back an error with the problematic fields

OpenAPI

Openapi doc and swagger is built in

    const server = await ServerBuilder.build({
        controllers: [UsersController],
        openapi: {
            swaggerUi: "api-docs", // swager ui route
            apiDocs: "docs", // raw json doc route
            spec: {info: {title: "test project", version: "1", description: "this is the test project decription"}, openapi: "3.0.0"}, // additional general informations
        }
    });

it will automatically add the routes, return types, request body etc. based on class validator classes.

  @Post()
  create(@Body() user: UserCreateDto): UserCreteResultDto {
  
  }

you can directly specify the result with a class validator claas:

  @ApiDocs({
    resultType: UserDto,
    summary: "custom summary",
    description: "this is my description",
    tags: ["user"]
  }) // and more..
  @Post()
  create(@Body() user: any): any {
  
  }

Environment variables

An easy to use helper for process.env variables

Env.asString(name, defaultValue) // string
Env.asNumber(name, defaultValue) // number
Env.asFloat(name, defaultValue) // number
Env.asArray(name, defaultValue) // string[]
Env.asArrayOfString(name, defaultValue) // string[] 
Env.asArrayOfNumber(name, defaultValue) // number[] 
Env.asArrayOfFloat(name, defaultValue) // number[] 

Error handling

We provide a helper class for creating http errors: HttpError you can throw built in http errors like BadRequestError, UnauthorizedError, ForbiddenError, InternalServerError, NotImplementedError

    @Get('/users')
    getTest() {
        throw new HttpError(404, "my message", {some: "details"})
        throw new NotImplementedError('my message') // provide proper status code, and status code description.
        throw new BadRequestError('my message') // provide proper status code, and status code description.
    }

Or create your own, just extend the HttpError class

DI container

This framework is built on type-chef-di. Visit the repo and learn more about it.