Built on express and type-chef-di
tsconfig.json
{
"emitDecoratorMetadata": true,
"experimentalDecorators": true
}
- Example of usage
- Prefix routes:
- Inject endpoint parameters
- Pipes
- Middlewares
- Validation
- OpenAPI
- Environment variables
- Error handling
- DI container
Table of contents generated with markdown-toc
npm install bonfire-rest
- 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.
- 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 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 {
// ...
}
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()
.
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) {
}
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) {
}
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) {
}
@Post("/users")
saveUser(@Req() req: Request) {
}
@Post("/users")
saveUser(@Res() res: Response) {
}
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
}
...
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"}
}
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 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 {
}
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[]
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
This framework is built on type-chef-di. Visit the repo and learn more about it.