NestJS –Nodejs Framework tuyệt vời thay thế ExpressJS

0

Trong một lần tình cờ nói chuyện với cô bạn đồng nghiệp cũ, mình được giới thiệu về một NodeJS framework khá hay ho. Có lẽ mọi người đã quá quen thuộc với ExpressJS framework đúng không? Hôm nay, mình sẽ giới thiệu một ứng cử viên thay thế sáng giá, đó là NestJS.

Trong bài viết này, mình sẽ hướng dẫn từng bước một, giải thích chi tiết cách xây dựng một dự án bằng NestJS, từ lúc cài đặt tới lúc triển khai ứng dụng.

Bạn đã sẵn sàng chưa? Bắt đầu thôi.

NestJS là gì?

NestJS là một Nodejs framework hiện đại dùng để xây dựng các ứng dụng trên server (ví dụ như tạo REST API chẳng hạn). Với ưu điểm nổi bật là tính hiệu quả, đáng tin cậy và dễ mở rộng, hỗ trợ Typescript. Đặc biệt là lớp bên dưới sử dụng ExpressJS nên nó kế thừa toàn bộ sức mạnh của framework phổ biến này.

Bạn có thể hiểu đơn giản là NestJS là một dự án mà tác giả muốn wapper lại mã nguồn, tối ưu và sắp xếp mã nguồn dựa trên NodeJS và ExpressJS. Nhờ đó mà mã nguồn dự án của bạn gọn gàng hơn, dễ bảo trì và tốc độ phát triển, xây dựng ứng dụng cũng nhanh hơn.

Giới thiệu NestJS cơ bản vậy thôi nhỉ! Chúng ta bắt tay vào thực hành cách tạo ứng dụng với NestJS thôi nhỉ.

Cài đặt NestJS

Trước khi có thể sử dụng NestJS để xây dựng ứng dụng, bạn cần phải cài đặt sẵn công cụ Nest CLI (Command Line Interface). Vẫn cách thức truyền thống, đó là sử dụng NPM để cài đặt.

$ npm i -g @nestjs/cli

Tất nhiên, do NestJS xây dựng trên NodeJS nên máy tính của bạn phải cài đặt sẵn Nodejs và NPM. Bạn tham khảo cách cài đặt Nodejs + NPM tại bài viết này: Cài đặt NodeJs trên Window, Ubuntu chi tiết

Ok, một khi Nest CLI đã cài đặt xong, chúng ta có thể sử dụng lệnh để tạo dự án NestJS:

$ nest new my-nestjs-01

Lệnh này sẽ tự động tạo một thư mục dự án với các file cần thiết để dự án có thể bắt đầu. Chúng ta sẽ cùng nhau xem cấu trúc dự án sẽ như nào nhé.

Project Structure

Cấu trúc ban đầu mà bạn tạo bằng lệnh Nest CLI sẽ như hình bên dưới đây:

nestjs-project-structure

Thư mục quan trọng nhất của dự án là src. Thư mục này sẽ chứa toàn bộ mã nguồn của dự án (mã nguồn có thể viết bằng typescript). Khi chúng ta xây dựng ứng dụng, thư mục này sẽ là nơi mà bạn làm việc thường xuyên nhất.

Bên trong thư mục src, bạn sẽ thấy các file được tạo sẵn:

  • main.ts: là file mà chúng ta sẽ sẽ khởi tạo các đối tượng để chạy ứng dụng, . Ví dụ: tại đây bạn sử dụng NestFactory.create() để khởi tạo instance của Nest
  • app.module.ts: Đây là module gốc của ứng dụng. Nó có trách nhiệm đóng gói tất cả mọi thứ của dự án.
  • app.controller.ts: Đây là controller gốc của route gốc. Trong trường hợp mặc định là nó xử lý cho route home page: http://domain:3000
  • app.service.ts: Chứa các hàm bạn xử lý logic cho service. Ví dụ ứng dụng bạn có service kết nối tới DB, hoặc server xử lý file.v.v…
  • app.controller.spec.ts: Là file dùng để test controller.

Như vậy, nhìn tổng quan, mã nguồn ứng dụng NestJS được chia thành 3 phần chính:

  • Modules
  • Controllers
  • Services

Chúng ta sẽ xem cụ thể mã nguồn bên trong thế nào nhé.

Đầu tiên là main.ts, mặc định khi tạo dự án bằng nest CLI, bạn sẽ có mã như sau:

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  await app.listen(3000);
}
bootstrap();

Như mình đã nói ở trên, main.ts là tệp chính khởi đầu của ứng dụng. Nhìn đoạn code trên, có lẽ mình không phải giải thích nhiều, bạn cũng hiểu được rằng đoạn code đó có tác dụng khởi tạo một server lắng nghe ở port 3000.

NestJS có một điểm hay mà mình rất thích, đó là tổ chức mã nguồn theo dạng module. Điều này giúp cho mã nguồn được phân tách thành những phần độc lập với nhau, giúp bạn dễ bảo trì và mở rộng ứng dụng. Để hiểu rõ hơn cách NestJS tổ chức theo module như nào, chúng ta sẽ khám phá tệp  app.module.ts

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';

@Module({
  imports: [],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

Để khai báo lớp AppModule theo kiểu module, bạn có thể sử dụng decorator @Module. Trong decorator này, bạn truyền option có 3 thuộc tính sau:

  • imports
  • controllers
  • providers

Tất cả các Controller thuộc một module sẽ phải khai báo trong thuộc tính controller ở trên.

Trong mã mặc định được tạo bởi Nest CLI, AppModule có duy nhất một controller: AppController.

import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get()
  getHello(): string {
    return this.appService.getHello();
  }
}

Controller này rất đơn giản, chỉ có duy nhất một GET route, sau này, bạn hoàn toàn có thể bổ sung thêm các route khác như POST, PUT, DELETE.

Trong ví dụ controller này, chúng ta có gọi tới một hàm của service:  appService.getHello();

Tại sao người ta không viết luôn việc xử lý logic rồi trả kết quả cho client mà lại phải tách ra service cho dài dòng nhỉ?

Cá nhân thì mình cho rằng: Nhiệm vụ của Controller chỉ có tiếp nhận dữ liệu từ request (param, query, body…) và trả dữ liệu cho client. Trong quá trình xử lý, nếu cần phải xử lý business logic như DB, File… Controller sẽ gọi tới service. Do đó, Service sẽ thuần là nghiệp business, tách bạch với công nghệ trong ứng dụng, và nó cũng là phần ít thay đổi về sau này nhất.

Đây là nội dung của app.service.ts

import { Injectable } from '@nestjs/common';

@Injectable()
export class AppService {
  getHello(): string {
    return 'Hello World!';
  }
}

Chạy ứng dụng NestJS

Để chạy ứng dụng, chúng ta gõ lệnh:

$ npm run start

Bạn sẽ thấy kết quả như hình dưới đây

nestjs-start-server

Nếu bạn vào trình duyệt truy cập vào http://localhost:3000 sẽ nhận kết quả:

nestjs-demo

Ngoài cách chạy trên, bạn còn cách khác nữa (dành cho developer)

$ npm run start:dev

Với cách chạy này, nodemon sẽ được khởi động kèm theo, lúc này khi bạn thay đổi bất kỳ trong code, nodemon sẽ tự động khởi động server để thay đổi có hiệu lực ngay mà bạn không cần phải thực hiện khởi động thủ công.

Thực hành tạo REST API với NestJS

Sau khi chúng ta đã tìm hiểu cơ bản cấu trúc một dự án NestJS, phần tiếp theo, chúng ta sẽ thực hành tạo một REST API quản lý khóa học online gồm đầy đủ các hành động CRUD như: GET, POST và DELETE.

Dưới đây là 4 endpoints tương ứng với hành động CRUD:

  • GET /courses: Lấy toàn bộ danh sách khóa học online
  • GET /courses/[courseId]: Lấy nội dung một khóa học thông qua ID
  • POST /courses: Thêm mới một khóa học
  • DELTE /courses: Xóa một khóa học

Chúng ta bắt đầu nhé.

Bước 1: Tạo một module

Như mình đã nói, NestJS tổ chức mã nguồn thành các module độc lập với nhau, nên việc đầu tiên là phải tạo một module. Bạn có thể dùng câu lệnh để Nest CLI tạo tự động:

$ nest generate module courses

Kết thúc câu lệnh trên, Nest sẽ tạo sẵn cho bạn một tệp: courses.module.ts với nội dung:

import { Module } from '@nestjs/common';

@Module({})
export class CoursesModule {}

Tất nhiên là chương trình Nest CLI không quên import module này vào AppModule:

import { CoursesModule } from './courses/courses.module';
@Module({
  imports: [CoursesModule],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

Quá gọn gàng phải không?

Bước 2: Tạo Controller

Tương tự như tạo module, chúng ta có thể dùng lệnh để tạo cho nhanh:

$ nest g controller courses

Kết quả là được file courses.controller.ts có nội dung:

import { Controller } from '@nestjs/common';
@Controller('courses')
export class CoursesController {}

Tất nhiên, đây chỉ là một controller rỗng, chưa có làm gì cả, phần tiếp theo là đợi bạn implement logic vào thôi.

Bước 3: Mock Data

Do chúng ta chưa có DB để lưu trữ dữ liệu, nên tạm thời mình sẽ mock data để giải lập dữ liệu trả về cho client nhé.

Trong thư mục src/courses, chúng ta tạo thêm tệp courses.mock.ts có nội dung như sau:

export const COURSES = [
  {
    id: 1,
    title: 'Lập trình NodeJS thật đơn giản',
    description: 'Học lập trình NODEJS từ đầu, tự xây dựng ứng dụng từ A-Z',
    author: 'Dương Anh Sơn',
    url: 'https://vntalking.com/sach-hoc-lap-trinh-node-js-that-don-gian-html',
  },
  {
    id: 2,
    title: 'Lập trình ReactJS thật đơn giản',
    description:
      'Trở thành front-end developer nhanh chóng bằng cách tiếp cận ReactJS từ A-Z',
    author: 'VNTALKING',
    url: 'https://vntalking.com/tai-lieu-hoc-reactjs-tieng-viet',
  },
  {
    id: 3,
    title: 'Javascript toàn tập',
    description: 'Học lập trình với Javascipt từ cơ bản tới nâng cao',
    author: 'Anthony Alicea',
    url: 'https://vntalking.com',
  },
];

Bước 4: Tạo mới Service

Phần service này sẽ có nhiệm vụ truy xuất vào dữ liệu để trả lại cho Controller. Để tạo service, vẫn dùng câu lệnh cho nhanh:

$ nest generate service courses

Nội dung mặc định được tạo như sau:

import { Injectable } from '@nestjs/common';

@Injectable()
export class CoursesService {}

Giờ chúng ta sẽ thêm code để nó lấy dữ liệu trong mock data:

import { Injectable, HttpException } from '@nestjs/common';
import { COURSES } from './courses.mock';

@Injectable()
export class CoursesService {
    courses = COURSES;
}

Lấy danh sách khóa học

Trong service, chúng ta sẽ cần phải implement hàm: getCourses()getCourse(courseId) tương ứng với việc lấy danh sách toàn bộ khóa học và lấy một khóa học từ ID.

getCourses(): Promise<any> {
     return new Promise(resolve => {
          resolve(this.courses);
     });
}

getCourse(courseId): Promise<any> {
     let id = Number(courseId);
     return new Promise(resolve => {
         const course = this.courses.find(course => course.id === id);
         if (!course) {
              throw new HttpException('Course does not exist', 404)
          }
          resolve(course);
     });
}

Thêm mới một khóa học

Phương thức tiếp theo trong service đó là thêm mới một khóa học: addCourse(course)

Hàm này sẽ trả về một Promise, và được resolve khi dữ liệu được thêm thành công.

addCourse(course): Promise<any> {
        return new Promise(resolve => {
            this.courses.push(course);
            resolve(this.courses);
        });
    }

Xóa một khóa học

Phần cuối cùng của service là xóa một khóa học: deleteCourse(courseId)

deleteCourse(courseId): Promise<any> {
        let id = Number(courseId);
        return new Promise(resolve => {
            let index = this.courses.findIndex(course => course.id === id);
            if (index === -1) {
                throw new HttpException('Course does not exist', 404);
            }
            this.courses.splice(index, 1);
            resolve(this.courses);
        });
    }

Bước 5: Cập nhật Controller

Sau khi chúng ta đã hoàn thành xong phần service, nhiệm vụ tiếp là cập nhật lại mã nguồn cho controller. Phần controller là nơi sẽ tiếp nhận request và trả response lại cho client. Đây là nội dung của courses.controller.ts

import { Controller, Get, Param, Post, Body, Delete, Query } from '@nestjs/common';
import { CoursesService } from './courses.service';
import { CreateCourseDto } from './create-course.dto';

@Controller('courses')
export class CoursesController {
    constructor(private coursesService: CoursesService) {}

    @Get()
    async getCourses() {
        const courses = await this.coursesService.getCourses();
    }

    @Get(':courseId')
    async getCourse(@Param('courseId') courseId) {
        const course = await this.coursesService.getCourse(courseId);
        return course;
    }

    @Post()
    async addCourse(@Body() createCourseDto: CreateCourseDto) {
        const course = await this.coursesService.addCourse(createCourseDto);
        return course;
    }

    @Delete()
    async deleteCourse(@Query() query) {
        const courses = await this.coursesService.deleteCourse(query.courseId);
        return courses;
    }
}

Trên đây là toàn bộ mã nguồn của phần controller. Tuy nhiên, còn một phần nữa mình muốn nói thêm. Đó là với POST request – dùng khi tạo mới một khóa học. Lúc này, dữ liệu client gửi lên sẽ được đặt trong body của request. Để truy cập và lấy dữ liệu từ body trong POST request, cách nhanh nhất là chúng ta sẽ sử dụng decorator  @Body().  Để xác định cấu trúc dữ liệu trong body, chúng ta sẽ sử dụng Data Transfer Object (DTO). Mã nguồn của file đó như sau: create-course.dto.ts

export class CreateCourseDto {
    readonly id: number;
    readonly title: string;
    readonly description: string;
    readonly author: string;
    readonly url: string;
}

Vậy là xong rồi đấy.

Các bạn download mã nguồn đầy đủ tại đây

Kiểm tra API với PostMan

Chạy thử server bằng câu lệnh: $ npm run start

Bạn có thể dùng ứng dụng Postman để kiểm tra các APIs nhé. Phần mềm này có lẽ cũng rất quen thuộc với mọi người rồi đúng không!?

Ví dụ: API lấy danh sách toàn bộ khóa học tương ứng với các URL:

nestjs-postman

 

Bài viết đến đây là kết thúc rồi, chúc bạn một ngày mới vui vẻ.

😃 Đọc thêm các bài viết khác:

Dịch vụ phát triển ứng dụng mobile giá rẻ - chất lượng

Bình luận. Cùng nhau thảo luận nhé!

avatar
  Theo dõi bình luận  
Thông báo