1.0.18 • Published 2 years ago
@altcrm/altexpress v1.0.18
ALTEXPRESS Backend Engine
! Attention ! The engine is in development. Functionality can be changed.
Get started
- Create a controller:
import { Request, Response, Controller, Get, Post, Put, Delete } from "@altcrm/altexpress";
@Controller("/clients")
export class ClientController {
@Get("/:id")
public async get(req: Request, res: Response): Promise<any> {
const client = await service.findById(req.params.id);
return { client };
}
}
- Create a module:
import { Module } from "@altcrm/altexpress";
@Module({
controllers: [ClientController]
})
export class ApplicationModule {}
- Create an application:
import { Application } from "@altcrm/altexpress";
import { ApplicationModule } from "./application.module";
async function startServer(port: number): Promise<void> {
try {
// create application and specify main module
const app = new Application(ApplicationModule);
// - !!! DO NOT USE !!! "express.urlencoded", "express.json" -
// - already defined inside the "Application":
// app.use(express.urlencoded({ extended: true }));
// app.use(express.json());
// - to specify global API prefix:
// app.setGlobalPrefix("/api/v1");
// - to specify middlewares:
// app.use(morgan("dev"));
// app.use(cors());
console.log("Starting API...");
await app.listen(port);
console.log(`Server is running on port ${port}`);
} catch (e) {
console.error(e);
}
}
// start the server
startServer(3000);
Tricks
Returning Method Result
- Simple. Just return a body (http status is
200
by default):
import { Request, Response, Controller, Post } from "@altcrm/altexpress";
@Controller("/clients")
export class ClientController {
@Post("/")
public async create(req: Request, res: Response): Promise<any> {
const client = await service.create(req.body.dto);
return { client };
}
}
- Custom status and body. Return
HttpResponse
object:
import { Request, Response, Controller, Post, HttpResponse } from "@altcrm/altexpress";
@Controller("/clients")
export class ClientController {
@Post("/")
public async create(req: Request, res: Response): Promise<any> {
const client = await service.create(req.body.dto);
return new HttpResponse(201, { client });
// or
// return HttpResponse.successCreated({ client });
}
}
- Error handling:
import { Request, Response, Controller, Post, HttpResponse, HttpException } from "@altcrm/altexpress";
@Controller("/clients")
export class ClientController {
@Post("/")
public async create(req: Request, res: Response): Promise<any> {
try {
// ...
} catch (e: any) {
return HttpResponse.error(e);
// or
// const err = new HttpException(400, e.message);
// return HttpResponse.error(err);
}
}
}
- Error handling by engine. Application will catch and handle exceptions:
import { Request, Response, Controller, Post, HttpResponse, HttpException } from "@altcrm/altexpress";
@Controller("/clients")
export class ClientController {
@Post("/")
public async create(req: Request, res: Response): Promise<any> {
// ...
throw new HttpException(403, "Forbidden");
// or
// throw new Error("Custom error.");
}
}
API Versioning
- Create controllers and modules for your business logic:
@Controller("/clients")
export class ClientControllerV1 {
@Get("/:id")
public async get(req: Request, res: Response): Promise<any> {
// ...
}
}
@Controller("/clients")
export class ClientControllerV2 {
@Get("/:id")
public async get(req: Request, res: Response): Promise<any> {
// ...
}
}
@Module({
controllers: [ClientControllerV1]
})
export class ClientModuleV1 {}
@Module({
controllers: [ClientControllerV2]
})
export class ClientModuleV2 {}
- Create modules for different API versions with prefix specified:
@Module({
prefix: "/api/v1",
modules: [ClientModuleV1]
})
export class ApiModuleV1 {}
@Module({
prefix: "/api/v2",
modules: [ClientModuleV2]
})
export class ApiModuleV2 {}
- Add modules to main application module:
@Module({
modules: [ApiModuleV1, ApiModuleV3]
})
export class ApplicationModule {}
Now the both routes are available:
/api/v1/clients/11111
/api/v2/clients/22222
Middlewares
- Create a middleware (for example, guard for authentication):
import passport from "passport";
export default class Guard {
public static Auth = passport.authenticate("jwt", { session: false });
}
- Add middleware to method decorator:
@Controller("/clients")
export class ClientController {
@Get("/:id", Guard.Auth)
public async get(req: Request, res: Response): Promise<any> {
// ...
}
}
Argument decorators
You can also use argument decorators to make your code cleaner:
@Controller("/clients")
export class ClientController {
@Get("/search", Guard.Auth)
public async search(
@Query("search") search: string
): Promise<Client[]> {
return await this.clientService.search(search);
}
@Get("/:id", Guard.Auth)
public async get(
@Params("id") id: string
): Promise<Client> {
return await this.clientService.get(id);
}
@Put("/:id", Guard.Auth)
public async update(
@Params("id") id: string,
@Body() dto: ClientUpdateDto
): Promise<Client> {
return await this.clientService.update(id, dto);
}
@Patch("/:id", Guard.Auth)
public async updatePhone(
@Params("id") id: string,
@Body("phone") phone: string
): Promise<Client> {
return await this.clientService.updatePhone(id, phone);
}
}
Use request and response objects
The controller is instantiated at each request. The instance has properties request
, response
, which can be accessed from every method. You can extends ControllerBase
class to access this properties from every method.
@Controller("/clients")
export class ClientController extends ControllerBase {
@Get("/:id", Guard.Auth)
public async get(): Promise<Client> {
const id = this.request.params.id // here
return await this.clientService.get(id);
}
}
Use dependency injection
You can use the dependency injection for controllers:
@Controller("/orders")
export class OrderController extends ControllerBase {
public constructor(
@Inject(OrderService) private readonly orderService: OrderService,
@Inject(ClientService) private readonly clientService: ClientService,
@Inject(DateTimeService) private readonly dateTimeService: DateTimeService,
) {
super();
}
}
or services:
export class OrderService {
public constructor(
@Inject(DateTimeService) private readonly dateTimeService: DateTimeService,
) { }
}
Dependencied should be registered in module's services
section:
@Module({
controllers: [
OrderController,
ClientController,
],
services: [
// Provide only the type for injection:
OrderService,
// It is an equivalent to:
{ type: ClientService, lifetime: ServiceLifetime.Scoped },
// You can provide concrete implementation for injection:
{ type: DateTimeService, concrete: DateTimeServiceFake, lifetime: ServiceLifetime.Singleton },
],
})
export class OrderModule {}
Service lifetime could be one of the next values:
Scoped
(default) - Service is instantiated once for every request.Transient
- Service is instantiated once for every call.Singleton
- Service is instantiated once for the entire module lifetime.