Skip to main content

Controllers

Controllers act as the primary interface between the client and server in ExpressoTS applications. They handle incoming requests, validate payloads against an input DTO, and emit responses in the DTO pattern. In essence, controllers bridge the communication between clients and service layers, also known as use-cases.

Controller MVC Pattern Example

Controller MVC Pattern

Controller Single Responsibility Pattern Example

Controller Single Responsibility Pattern

In order to create a basic controller we create a class and use the @controller() decorator to define the route for the controller. Then we create a method and use the http Methods decorators to define the route and HTTP method for the method.

Controller Class

MVC Pattern

Multiple routes in a single controller.

@controller("/user")
export class UserController {
@Post("/")
create(@response() res: Response) {
return res.send("User created");
}

@Get("/")
getUser(@response() res: Response) {
return res.send("User listed");
}
}

Single Responsibility Pattern

One controller, one execution method, and one use case.

@controller("/user/create")
export class UserCreateController {
@Post("/")
execute(@response() res: Response) {
return res.send("User created");
}
}
tip

Both, the @controller() and http methods decorators works in combination to define the route and middlewares to be used in the endpoint.

Controller Scope

The default scope of a controller is Request, as it is inherited from the AppContainer and default Module class scope. However, you can override the scope of a controller by using the @scope() decorator. This decorator accepts the same BindingScopeEnum enum values.

info

If you define the module scope you can not override it in a specific controller by using the @scope decorator. The @scope decorator can only be used in specific controllers if the module scope is not defined.

Here is an example of usage:

@scope(BindingScopeEnum.Singleton)
@controller("/")
class CreateUserController extends BaseController {}

The controller above will be scoped as Singleton and will be shared across all requests.

BaseController Class

We also power a controller class with the BaseController class that offers in the constructor a parameter that the developer can indicate what service or domain he is currently building. This helps the log system throw errors with more information about the context of the error and where the error occurred.

Another advantage of using the BaseController class is that it offers a method in two different versions async and sync, which is the callUseCase() or callUseCaseAsync().

These methods reinforce the idea of one use case per controller, and they are responsible for calling the use case that will implement the business logic of the controller.

tip

The callUseCase methods, available in both synchronous and asynchronous versions, are well-suited for returning status and JSON responses to clients, as the users just need to pass the useCase return, res:Response, and statusCode.

The signature of the callUseCase method:

callUseCase(useCase: any, res: any, successStatusCode: number);
  • useCase: The use case that will be called. This use case is injected in the controller constructor.
  • res: The response object from the express request.
  • successStatusCode: The status code that will be sent to the client if the use case is executed successfully. Please see the Http Status Code type for more information.

The default response:

return res.status(successStatusCode).json(dataReturn);

A more complete example of a controller class inheriting from the BaseController class is shown below:

@controller("/")
class AppController extends BaseController {
constructor(private appUseCase: AppUseCase) {
super("app-controller");
}

@httpGet("/")
execute(@response() res: any): IAppResponseDTO {
return this.callUseCase(this.appUseCase.execute(), res, StatusCode.OK);
}
}

Controller Decorators

HTTP methods and parameters decorators are a set of annotations used in ExpressoTS applications to define the routing and request handling for HTTP requests. Using the decorators listed below can simplify the routing and handling of HTTP requests in ExpressoTS applications, and make the code more readable and maintainable.

HTTP Methods Decorators

Here's a list of all available @httpMethods() decorators in ExpressoTS, along with their description and usage:

DecoratorDescriptionUsage
@GetBinds a controller method to a GET HTTP verb.@Get("/path")
@PostBinds a controller method to a POST HTTP verb.@Post("/path")
@PutBinds a controller method to a PUT HTTP verb.@Put("/path")
@PatchBinds a controller method to a PATCH HTTP verb.@Patch("/path")
@HeadBinds a controller method to a HEAD HTTP verb.@Head("/path")
@DeleteBinds a controller method to a DELETE HTTP verb.@Delete("/path")
@MethodBinds a controller method to a specified HTTP verb.@Method("verb", "/path")

Parameter Decorators

Here's a list of all available parameter decorators in ExpressoTS, along with their description and usage:

DecoratorDescriptionUsage
@request()Injects the Express Request object.execute(@request() req: Request)
@response()Injects the Express Response object.execute(@response() res: Response)
@param(paramName?: string)Injects a parameter from the request URL path.execute(@param('id') id: string)
@query(paramName?: string)Injects a parameter from the request URL query string.execute(@query('searchTerm') searchTerm: string)
@body()Injects the request body payload.execute(@body() body: MyDTO)
@headers(headerName?: string)Injects a header from the request headers.execute(@headers('authorization') auth: string)
@cookies(cookieName?: string)Injects a cookie from the request cookies.execute(@cookies('session') session: string)
@next()Injects the Express NextFunction object.execute(@next() next: NextFunction)

DTO Pattern

Data Transfer Object (DTO) is a commonly used design pattern that standardizes data formats for communication between different application layers, including client-server or server modules. DTO serves as an interface for data exchange, ensuring clear and standardized structures for input and output data. By separating business logic from communication logic, it helps to reduce application complexity and decouple different layers.

Using DTOs can improve application performance and scalability by reducing the data transfer between the client and server and providing more efficient ways to process and handle data within the application.

DTO Example

For example, imagine a user registration scenario where name, email, and password are collected, and ID is auto-generated. The user object DTO for Input and Response could have possible formats, as shown below:

// UserCreateInputDTO
interface UserCreateInputDTO {
name: string;
email: string;
password: string;
}

// UserCreateResponseDTO
interface UserCreateResponseDTO {
id: string;
name: string;
status: string;
}

// Payload json format
{
"name": string,
"email": string,
"password": string
}

Having two different DTO formats is essential in this case because while registering a user, we don't want to return the password to the client due to security concerns. Therefore, we created a different DTO for the response, adding a complementary field called status, where a message is typically sent to the client, indicating that the user was created successfully.

As a result, DTOs help to segregate and filter the data being sent to the client, reducing the amount of data processed by the application.


Support the Project

ExpressoTS is an MIT-licensed open source project. It's an independent project with ongoing development made possible thanks to your support. If you'd like to help, please consider: