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é ^_^
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é.
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é.
Ở bài viết này mình sẽ hướng dẫn bạn bắt đầu xây dựng một ứng dụng HMVC với Laravel, và tận dụng sức mạnh của Composer khi quản lí modules.

Mục lục

Related posts

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.
Đây là một khái niệm rất quan trọng trong Functional Programming. Ở đây mình sẽ cho ví dụ dựa trên Javascript, cụ thể là TypeScript, do đó mình hi vọng các bạn đã có một số kiến thức nhất định về JS trước. Điều này sẽ giúp bạn nắm bắt nội dung bài viết dễ dàng hơn.
NestJS - Providers
1434
Providers là thành phần cơ bản và cực kỳ quan trọng trong Nest để thực hiện Dependency Injection.
Một số design patterns chính đang được sử dụng bởi NestJS mà bạn cần nắm rõ để làm việc với NestJS hiệu quả hơn.
Một số design patterns chính đang được sử dụng bởi NestJS mà bạn cần nắm rõ để làm việc với NestJS hiệu quả hơn.
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é.
NestJS là một NodeJS framework tiến bộ để xây dựng các ứng dụng server-side hiệu quả, đáng tin cậy cùng với khả năng mở rộng dễ dàng.

Tin mới nhất

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
Bài viết này sẽ hướng dẫn các bạn cài đặt LEMP stack trên CentOS Stream 9 mới nhất trên Vultr Cloud VPS.
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.
Đây là một khái niệm rất quan trọng trong Functional Programming. Ở đây mình sẽ cho ví dụ dựa trên Javascript, cụ thể là TypeScript, do đó mình hi vọng các bạn đã có một số kiến thức nhất định về JS trước. Điều này sẽ giúp bạn nắm bắt nội dung bài viết dễ dàng hơn.
Bài hướng dẫn này sẽ hướng dẫn các bạn cài đặt LEMP stack (Linux - Nginx - MySQL - PHP) trên CentOS 8.
NestJS - Providers
1434
Providers là thành phần cơ bản và cực kỳ quan trọng trong Nest để thực hiện Dependency Injection.
Một số design patterns chính đang được sử dụng bởi NestJS mà bạn cần nắm rõ để làm việc với NestJS hiệu quả hơn.
Một số design patterns chính đang được sử dụng bởi NestJS mà bạn cần nắm rõ để làm việc với NestJS hiệu quả hơ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.
NestJS - Providers
1434
Providers là thành phần cơ bản và cực kỳ quan trọng trong Nest để thực hiện Dependency Injection.
Một số design patterns chính đang được sử dụng bởi NestJS mà bạn cần nắm rõ để làm việc với NestJS hiệu quả hơn.
Một số design patterns chính đang được sử dụng bởi NestJS mà bạn cần nắm rõ để làm việc với NestJS hiệu quả hơn.
NestJS - Controllers
1757
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.