NestJS - Controllers

Trách nhiệm chính của controllers là xử lý các requests và phản hồi lại cho phía client.
NestJS - Controllers
NestJS - Controllers

NestJS sử dụng một design pattern ở hầu hết mọi nơi, đó là Decorator (nếu ai từng làm việc với Angular chắc hẳn đều đã quen thuộc với design pattern này).

Nếu bạn chưa nắm rõ phần này, bạn có thể tham khảo thêm ở link sau đây Decorators trong Typescript

nestjs-controllers-1.png

Mục đích chính của controllers là tiếp nhận các request cụ thể từ ứng dụng. Cơ chế routing sẽ chỉ định các controllers nào cần tiếp nhận và xử lý requests. Thông thường, mỗi controller sẽ có nhiều routes, mỗi routes chịu trách nhiệm xử lý các actions cụ thể.

Để khởi tạo nhanh các CRUD controllers đi kèm với bộ validation có sẵn, bạn có thể sử dụng đoạn CLI sau:

$ nest g resource your-resource-name

 

Routing

Bạn có thể khai báo một Controller bằng cách tạo một class kèm theo decorator @Controller(). Chúng ta cũng có thể group các controllers lại với nhau thông qua việc chỉ định optional path cho decorator @Controller. Điều này cũng sẽ giúp bạn giảm thiểu code lặp không cần thiết.

// cats.controller.ts

import { Controller, Get } from '@nestjs/common';

@Controller('cats')
export class CatsController {
  @Get()
  findAll(): string {
    return 'This action returns all cats'
  }
}

Bạn có thể tạo một controller bằng CLI như sau

$ nest g controller your-controller-name

Decorator @Get() được khai báo phía trên method findAll() giúp Nest xác định và tiến hành điều hướng xử lý requests. 

Bởi vì bạn đã khai báo prefix path cho controller là cats, và bạn cũng để trống phần path trong decorator @Get(), nên khi bạn truy cập link http://localhost:3000/cats qua phương thức GET, Nest sẽ điều hướng xử lý tới method findAll() mà bạn đã khai báo trong controller.

Cũng khá dễ hiểu đúng không ^_^

Theo mặc định thì method này sẽ trả về dữ liệu kèm theo Http Status Code 200. Bạn cũng có thể thay đổi chúng nếu muốn.

 

Thông tin Request

Trong vài trường hợp, bạn cần lấy một số thông tin chi tiết từ request phía client. Nest cung cấp cho chúng ta decorator @Req() để lấy thông tin từ client request. Biến này sẽ là Request (nếu bạn sử dụng platform Express), hoặc FastifyRequest (nếu bạn sử dụng platform Fastify),... 

import { Controller, Get, Req } from '@nestjs/common';
import { Request } from 'express';

@Controller('cats')
export class CatsController {
  @Get()
  findAll(@Req() request: Request): string {
    return 'This action returns all cats'
  }
}

Request Object của Nest chứa rất nhiều thông tin, và trong hầu hết các trường hợp chúng ta cũng không cần phải lấy các thông tin này một cách thủ công. Do đó, Nest cung cấp cho chúng ta một số request decorators khác giúp chúng ta dễ dàng thao tác và xử lý hơn.

  • @Request(), @Req() ==> req
  • @Response(), @Res() ==> res

Lưu ý: khi sử dụng @Response() hoặc @Res(), bạn cần tự quản lý việc phản hồi, sẽ không làm điều đó cho bạn. Ví dụ, bạn cần gọi res.json()... hoặc res.send()..., nếu không hệ thống sẽ bị treo vĩnh viễn.

Hoặc bạn cũng có thể sử dụng thiết lập @Res({ passthrough: true }). Lúc này, bạn không cần phải tự quản lý việc phản hồi, mà chỉ tập trung vào việc chỉnh sửa một số thông tin khác trên response. Phần còn lại, Nest sẽ làm giúp bạn.

  • @Next() ==> next
  • @Session() ==> req.session
  • @Param(key?: string) ==> req.params / req.params[key]
  • @Body(key?: string) ==> req.body / req.body[key]
  • @Query(key?: string) ==> req.query / req.query[key]
  • @Headers(name?: string) ==> req.headers / req.headers[name]
  • @Ip() ==> req.ip
  • @HostParam() ==> req.hosts

 

HTTP methods

Nest cung cấp các một số decorators giúp thiết lập routing qua HTTP methods.

  • @Get() dùng để lấy dữ liệu.
  • @Post() cơ bản là tạo mới thông tin.
  • @Put() thường được dùng để cập nhật thông tin một bản ghi. Nên nhớ là nó sẽ thay thế bản ghi bằng nguyên cục data bạn truyền vào.
  • @Patch() cũng được dùng để cập nhật thông tin. Nhưng khác ở chỗ, nó chỉ cập nhật một vài fields được yêu cầu thay vì toàn bộ.
  • @Delete() xóa dữ liệu.
  • @All() phương thức này chấp nhận mọi HTTP methods. 

Ngoài ra còn có @Options(), @Head()... nhưng mình hầu như chưa sử dụng ^_^.

 

HTTP Status Code

Theo mặc định, repsonse code luôn luôn là 200, đối với các request POST201. Bạn có thể thay đổi nó một cách dễ dàng thông qua decorator @HttpCode(...)

@Post()
@HttpCode(204)
create() {
  return 'This action adds a new cat'
}

Đôi lúc, response code của bạn không cố định, mà phụ thuộc vào các yếu tố khác nhau. Trong trường hợp đó, bạn có thể chỉ định chúng một cách dynamic thông qua @Res().

Bạn cần import decorator HttpCode từ package @nestjs/common.

 

Headers

Để tùy chỉnh một Header, bạn có thể dùng decorator @Header()

@Post()
@Header('Cache-Control', 'none')
create() {
  return 'This action adds a new cat'
}

Hoặc thông qua @Res()

@Post()
create(@Res() res) {
  res
    .header('Cache-Control', 'none')
    .send('This action adds a new cat')
}

Bạn có thể import decorator Header từ package @nestjs/common.

 

Redirect

Để redirect tới một URL cụ thể, bạn có thể sử dụng decorator @Redirect() hoặc thông qua @Res(), sau đó call trực tiếp res.redirect().

Decorator @Redirect() nhận 2 optional params là url: stringstatusCode: number. Giá trị mặc định của statusCode302.

@Get()
@Redirect('https://duypt.dev', 301)

Để thực hiện redirect một cách dynamic, bạn có thể sử dụng cách sau

@Get('docs')
@Redirect('https://docs.nestjs.com', 301)
getDocs(@Query('version') version) {
  if (version && version === '5') {
    return { url: 'https://docs.nestjs.com/v5/', statusCode: 301 }
  }
}

Giá trị trả về sẽ override giá trị bạn truyền vào decorator @Redirect().

 

Route parameters

Khai báo routes với path tĩnh sẽ không hoạt động khi bạn cần truyền dynamic data trực tiếp vào URL. 

Để khai báo một route chấp nhận dynamic params, bạn có thể làm như sau

@Get(':id')
findOne(@Param('id') id): string {
  return `This action returns a #${id} cat`
}

Nếu bạn không khai báo name cho decorator @Param(), nó sẽ trả về toàn bộ route params nếu tồn tại.

Bạn cần import decorator Param từ package @nestjs/common.

 

Request scopes

Đối với một số bạn có nền tảng kiến thức từ một số ngôn ngữ lập trình khác (như PHP...), có thể họ sẽ khá bất ngờ khi mà hầu hết mọi thứ đều được "xài chung" trong các requests. Chúng ta có một cái connection tới database, một số singleton services được dùng toàn cục...

Hãy nhớ rằng, NodeJS không tuân theo mô hình Multi-Threaded Stateless Model (nơi mà mỗi yêu cầu được xử lý trên một thread riêng biệt). Do đó, việc sử dụng singleton hoàn toàn an toàn với Nest.

Tuy nhiên, đôi lúc bạn mong muốn một số service chỉ hoạt động trong phạm vi một request cụ thể nào đó để tránh việc chồng chéo lên nhau. Đó là lúc bạn cần quan tâm tới request scopes

Mình sẽ đi sâu hơn về phần này trong các phần tiếp theo.

 

Bất đồng bộ (Asynchronicity)

Hầu hết các công việc thao tác với dữ liệu hoặc hệ thống khác đều là bất đồng bộ. NestJS cũng hỗ trợ và hoạt động rất tốt với async.

Tất cả các async functions đều trả về một Promise. Nest sẽ tự giải quyết phần xử lý cho bạn.

@Get()
async findAll(): Promise<any[]> {
  return []
}

Ngoài ra, Nest còn hỗ trợ một framework xử lý bất đồng bộ cực kỳ mạnh mẽ - RxJS theo cơ chế Observable streams hiện đại. Nest sẽ tự động subscribe để lấy về giá trị cuối cùng khi stream completed.

@Get()
findAll(): Observable<any[]> {
  return of([])
}

Bạn sử dụng kiểu nào cũng được, nhưng cá nhân mình đề nghị bạn dùng Observable nhé ^_^. Cứ xài thử và bạn sẽ mê mẩn ngay kkk.

Mình sẽ viết một vài bài chi tiết về RxJS sau nhé ^_^.

 

Truyền nhận dữ liệu qua DTO

Nest hỗ trợ chúng ta đọc dữ liệu truyền lên từ client request thông qua decorator @Body()

Bạn cần lưu ý là theo chuẩn HTTP, các requests sử dụng method POST, PUT, PATCH mới có thể đính kèm payload, do đó decorator @Body() chỉ hoạt động với các HTTP methods này.

DTO (Data Transfer Object) - là cách triển khai một design pattern rất phổ biến - Transfer Object Pattern. Chúng đơn giản là các object xác định kiểu dữ liệu sẽ được gửi đi hoặc nhận về, và có thể được serialize khi truyền qua mạng. Chúng không - và cũng không nên có các logic xử lý bên dưới.

Nest hỗ trợ chúng ta khai báo DTO qua interface hoặc các class. Tuy nhiên, mình khuyên các bạn hãy sử dụng class.

  • Class là một phần trong chuẩn Javascript ES6. Do đó, chúng được giữ lại sau khi compiled.
  • Trong khi đó interface chỉ là cú pháp của Typescript, chúng không tồn tại, và cũng sẽ bị xóa khỏi code Javascript sau khi compiled. Do đó, Nest không thể tham chiếu đến chúng trong lúc chạy.
  • Thêm một vài điểm nhấn quan trọng khi bạn sử dụng class:
    • Rất dễ dàng thực hiện validate dữ liệu đầu cuối thông qua class-validator.
    • Có thể transform dữ liệu thông qua các Pipes. Bởi class là tồn tại, do đó chúng ta có thể can thiệp vào các metadata của chúng trong thời gian chạy.
//create-cat.dto.ts
export class CreateCatDto {
  name: string
  age: number
  breed: string
}

//cats.controller.ts
@Post()
async create(@Body() createCatDto: CreateCatDto) {
  return 'This action adds a new cat'
}

Bạn cũng có thể thêm một vài validation như thế này

//create-cat.dto.ts
export class CreateCatDto {
  @IsNotEmpty()
  name: string

  @IsInt()
  @Optional()
  age: number

  @MinLength(5)
  breed: string
}

Vừa dễ lại vừa clean đúng không ^_^.

 

Xử lý Exceptions

Nest hỗ trợ bạn rất nhiều trong việc xử lý Exceptions. Mình sẽ trình bày thêm phần này trong các bài tiếp theo nhé ^_^

 

Kết

Mình xin phép kết thúc phần giới thiệu cơ bản về controllers ở đây. Hẹn gặp lại các bạn trong các bài tiếp theo nhé.

Comments

Bài viết nổi bật

Dạo gần đây đi đâu cũng nghe nói về microservices, người người nhà nhà rục rịch chuyển dịch hệ thống sang microservices. Trước khi đưa ra sự so sánh, mình sẽ khái quát một chút về Monolith Application và MicroServices một chút cho các bạn chưa biết nắm rõ hơn nhé.
PHP là ngôn ngữ được sử dụng rộng rãi nhất trên thế giới trong lập trình web. Nó cũng bị ghét nhất. Nhưng tại sao nhiều developer lại ghét nó đến vậy? Hôm nay chúng ta hãy cùng tìm hiểu lý do xem chúng có thuyết phục không nhé ^_^
Lúc trước mình hay sử dụng cách này trên laptop phụ của mình, giờ mua license luôn rồi. Hôm nay mình xin chia sẻ cho bạn nào cần nhé.
JWT Tokens là một cách thức lưu trữ thông tin xác thực hiệu quả, nhưng làm cách nào để chúng ta có thể giúp chúng an toàn hơn? Có 2 cách thường dùng để lưu trữ JWT Tokens là LocalStorage và Cookies. Bây giờ chúng ta sẽ bắt đầu "mổ xẻ" các ưu - nhược điểm của mỗi loại nhé.
Có khá nhiều bạn đã yêu cầu mình một bài viết về Repository Design Pattern. Vậy mục đích của nó là gì? Nó có thực sự cần thiết cho ứng dụng của bạn hay không? Những điểm mạnh, điểm yếu của nó là gì? Chúng ta cùng đi sâu tìm hiểu qua bài viết này nhé.

Mục lục

Related posts

Triển khai Saga Pattern trong microservices với NodeJS và Choreography-Based Saga
Mô hình Saga đưa ra một giải pháp có cấu trúc để giải quyết thách thức này. Nó cung cấp một phương pháp có hệ thống để quản lý transaction qua nhiều microservices. Điều này giải quyết những phức tạp của các transaction phân tán và hoàn toàn tương thích với các nguyên tắc của kiến trúc microservices, được đặc trưng bởi sự kết nối lỏng lẻo và khả năng triển khai độc lập của các service.
Một API cho phép giao tiếp hai chiều giữa các ứng dụng phần mềm thông qua các requests. Một Webhook là một API nhẹ, hỗ trợ chia sẻ dữ liệu một chiều được kích hoạt bởi các events.
Một trong những câu hỏi được đặt thường xuyên nhất về TypeScript là liệu chúng ta nên sử dụng interface hay type. Câu trả lời cho câu hỏi này, giống như nhiều câu hỏi lập trình khác, là nó phụ thuộc vào tình hình cụ thể. Trong một số trường hợp, một cái có lợi thế rõ rệt hơn cái kia, nhưng trong nhiều trường hợp, chúng có thể thay thế cho nhau.
Đây là các types cơ bản nhưng cũng phổ biến nhất trong Typescript. Một số types khác phức tạp hơn cũng được xây dựng dựa trên những types cơ bản này.
Trong thế giới lập trình, trách nhiệm lớn nhất của chúng ta không phải chỉ làm cho code chạy được, mà còn phải đảm bảo rằng các đoạn code mà chúng ta viết có thể dễ dàng kiểm tra và bảo trì trong một khoảng thời gian dài.
Phân trang - một thành phần không thể thiếu trong các ứng dụng có lượng dữ liệu lớn. Tuy nhiên, bạn hiểu được bao nhiêu về nó?
Javascript là một thành phần không thể thiếu đối với frontend developers. Tuy nhiên, ngay từ lúc ra đời, nó đã tồn tại khá nhiều vấn đề cần khắc phục. Đó là lý do tại sao từ 2015 (ES6) tới 2021 (ES12) ra đời nhằm giúp Javascript trở nên tốt hơn.
Dạo này mình làm việc với mấy bạn trên github, thấy hay xài mấy từ viết tắt mà mình không hiểu lắm. Thôi thì tổng hợp lại một list các từ viết tắt hay dùng trong github luôn cho ai cần :D
Dạo gần đây đi đâu cũng nghe nói về microservices, người người nhà nhà rục rịch chuyển dịch hệ thống sang microservices. Trước khi đưa ra sự so sánh, mình sẽ khái quát một chút về Monolith Application và MicroServices một chút cho các bạn chưa biết nắm rõ hơn nhé.
Cách bỏ qua câu lệnh --set-upstream quen thuộc cho các con lười
Mình sẽ giới thiệu 2 cách để xóa một property trong Javascript Object. Một cách sử dụng mutable - toán tử delete, một cách còn lại là immutable - tính năng Object Restructuring.

Tin mới nhất

Triển khai Saga Pattern trong microservices với NodeJS và Choreography-Based Saga
Mô hình Saga đưa ra một giải pháp có cấu trúc để giải quyết thách thức này. Nó cung cấp một phương pháp có hệ thống để quản lý transaction qua nhiều microservices. Điều này giải quyết những phức tạp của các transaction phân tán và hoàn toàn tương thích với các nguyên tắc của kiến trúc microservices, được đặc trưng bởi sự kết nối lỏng lẻo và khả năng triển khai độc lập của các service.
Một API cho phép giao tiếp hai chiều giữa các ứng dụng phần mềm thông qua các requests. Một Webhook là một API nhẹ, hỗ trợ chia sẻ dữ liệu một chiều được kích hoạt bởi các events.
Một trong những câu hỏi được đặt thường xuyên nhất về TypeScript là liệu chúng ta nên sử dụng interface hay type. Câu trả lời cho câu hỏi này, giống như nhiều câu hỏi lập trình khác, là nó phụ thuộc vào tình hình cụ thể. Trong một số trường hợp, một cái có lợi thế rõ rệt hơn cái kia, nhưng trong nhiều trường hợp, chúng có thể thay thế cho nhau.
Trong phần này, chúng ta sẽ tìm hiểu một số khái niệm cơ bản nhất về AWS là gì và một số lợi ích khi sử dụng AWS.
Trở thành một software developer hiệu suất cao không phải là điều dễ dàng. Điều này đòi hỏi bạn phải có kỹ năng và kiến thức về lập trình, cũng như cách tiếp cận và giải quyết các vấn đề phức tạp. Tuy nhiên, nếu bạn có chút kiên nhẫn và sự nỗ lực, bạn hoàn toàn có thể trở thành một developer tài năng và thành công.
Đây là các types cơ bản nhưng cũng phổ biến nhất trong Typescript. Một số types khác phức tạp hơn cũng được xây dựng dựa trên những types cơ bản này.
Trong thế giới lập trình, trách nhiệm lớn nhất của chúng ta không phải chỉ làm cho code chạy được, mà còn phải đảm bảo rằng các đoạn code mà chúng ta viết có thể dễ dàng kiểm tra và bảo trì trong một khoảng thời gian dài.
Thông tin được định nghĩa dưới dạng dữ liệu, kiến thức về thông tin, và trí tuệ về tri thức.
Phân trang - một thành phần không thể thiếu trong các ứng dụng có lượng dữ liệu lớn. Tuy nhiên, bạn hiểu được bao nhiêu về nó?
Javascript là một thành phần không thể thiếu đối với frontend developers. Tuy nhiên, ngay từ lúc ra đời, nó đã tồn tại khá nhiều vấn đề cần khắc phục. Đó là lý do tại sao từ 2015 (ES6) tới 2021 (ES12) ra đời nhằm giúp Javascript trở nên tốt hơn.
Dạo này mình làm việc với mấy bạn trên github, thấy hay xài mấy từ viết tắt mà mình không hiểu lắm. Thôi thì tổng hợp lại một list các từ viết tắt hay dùng trong github luôn cho ai cần :D
Triển khai Saga Pattern trong microservices với NodeJS và Choreography-Based Saga
Một trong những câu hỏi được đặt thường xuyên nhất về TypeScript là liệu chúng ta nên sử dụng interface hay type. Câu trả lời cho câu hỏi này, giống như nhiều câu hỏi lập trình khác, là nó phụ thuộc vào tình hình cụ thể. Trong một số trường hợp, một cái có lợi thế rõ rệt hơn cái kia, nhưng trong nhiều trường hợp, chúng có thể thay thế cho nhau.
Đây là các types cơ bản nhưng cũng phổ biến nhất trong Typescript. Một số types khác phức tạp hơn cũng được xây dựng dựa trên những types cơ bản này.
Trong thế giới lập trình, trách nhiệm lớn nhất của chúng ta không phải chỉ làm cho code chạy được, mà còn phải đảm bảo rằng các đoạn code mà chúng ta viết có thể dễ dàng kiểm tra và bảo trì trong một khoảng thời gian dài.
Javascript là một thành phần không thể thiếu đối với frontend developers. Tuy nhiên, ngay từ lúc ra đời, nó đã tồn tại khá nhiều vấn đề cần khắc phục. Đó là lý do tại sao từ 2015 (ES6) tới 2021 (ES12) ra đời nhằm giúp Javascript trở nên tốt hơn.