+9

Xây dựng REST API với NestJS và Prisma (Phần 1)

NestJS là một trong những framework Node.js nổi bật và nó đã nhận được rất nhiều sự yêu thích cũng như sức hút của các lập trình viên. NestJS giúp dễ dàng xây dựng các ứng dụng web hiện đại thường đòi hỏi việc tạo ra các REST API để tương tác với dữ liệu từ các máy chủ.

Giới thiệu

NestJS là một framework phát triển ứng dụng Node.js có cấu trúc, dựa trên các nguyên lý của Angular. Nó sử dụng TypeScript, một phiên bản được cải tiến của JavaScript, giúp kiểm soát và dễ dàng triển khai mã nguồn. Với việc triển khai mô hình kiến trúc lõi (core architecture), cấu trúc module, dependency injection và một số tính năng mạnh mẽ khác, mục tiêu chính của NestJS là cung cấp một cấu trúc ứng dụng rõ ràng và dễ quản lý, giúp tăng tính bảo trì và sự tổ chức trong mã nguồn.

Nội dung bài viết này sẽ trình bày các bước cơ bản để chúng ta có thể tạo ra REST API với NestJS, Prisma, PostgreSQL.

Các công nghệ sẽ sử dụng:

  • Backend framework: NestJS
  • Object-Relational Mapper (ORM): Prisma
  • Database: PostgreSQL
  • Programming language: Typescript

Về cơ bản sẽ có các bước sau:

  1. Đầu tiên, tạo một dự án NestJS mới.
  2. Sau đó, khởi tạo máy chủ PostgreSQL và kết nối với nó bằng Prisma.
  3. Cuối cùng, xây dựng các API.

Các bước thực hiện

Setup môi trường

  • Cài đặt Node.js.
  • Cài đặt PostgreSQL.
  • Cài đặt Extension: Prisma VSCode. (không bắt buộc)

1. Generate NestJS Project

Đầu tiên, ta phải cài đặt NestJS CLI. Để bắt đầu, hãy chạy lệnh sau ở vị trí bạn muốn đặt dự án:

npx @nestjs/cli new nest-starter

CLI sẽ nhắc bạn chọn trình quản lý gói cho dự án - chọn cái bạn muốn. Mình sẽ chọn npm.

Sau đó, bạn sẽ có một dự án NestJS mới trong thư mục hiện tại. Bạn sẽ thấy các tập tin sau:

nest-starter
  ├── node_modules
  ├── src
  │   ├── app.controller.spec.ts
  │   ├── app.controller.ts
  │   ├── app.module.ts
  │   ├── app.service.ts
  │   └── main.ts
  ├── test
  │   ├── app.e2e-spec.ts
  │   └── jest-e2e.json
  ├── README.md
  ├── nest-cli.json
  ├── package-lock.json
  ├── package.json
  ├── tsconfig.build.json
  └── tsconfig.json

Hầu hết code sẽ nằm trong thư mục src.

Một số trong những thành phần đáng chú ý là:

  • src/app.module.ts: Mô-đun gốc của ứng dụng.
  • src/app.controller.ts: Basic controller với route: /. Router này sẽ trả về 'Hello world!'.
  • src/main.ts: Entry point của ứng dụng. Nó sẽ khởi động NestJS.

Bạn có thể khởi động bằng cách sử dụng lệnh sau:

npm run start:dev

Lệnh này sẽ theo dõi các tập tin trong dự án, tự động biên dịch lại và reload server bất cứ khi nào thực hiện thay đổi.

Truy cập URL http://localhost:3000/. để tới trang 'Hello World!'.

2. Setup Prisma

Hãy đảm bảo Postgres của bạn đã sẵn sàng!

Initialize Prisma

Chạy command sau để cài đặt Prisma:

npm install -D prisma

Bạn có thể khởi tạo Prisma trong dự án của mình bằng cách chạy:

npx prisma init

Thao tác này sẽ tạo một thư mục prisma file schema.prisma. Đây là tệp config database. Lệnh này cũng tạo một tệp .env bên trong dự án.

Set env variable

Hãy config lại DATABASE_URL để kết nối với PostgreSQL. Ví dụ:

DATABASE_URL="postgresql://myuser:password@localhost:5432/mydb?schema=nest-db"

Các thành phần trong Prisma schema

// prisma/schema.prisma
generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

Tệp này được viết bằng Prisma schema, đây là ngôn ngữ mà Prisma sử dụng để xác định schema cho database của bạn.

Tệp Schema.prisma có ba thành phần chính:

  • Data source: Chỉ định kết nối db. Config trên có nghĩa là nhà cung cấp db của bạn là PostgreSQL và chuỗi kết nối db có sẵn trong biến env DATABASE_URL.
  • Generator: Cho biết rằng bạn muốn tạo Prisma Client, trình tạo truy vấn loại an toàn cho cơ sở dữ liệu của bạn. Nó được sử dụng để gửi truy vấn đến cơ sở dữ liệu của bạn.
  • Data mode: Xác định các model của bạn. Mỗimodel sẽ được ánh xạ tới một bảng trong Database. Ví dụ trên chưa có chứa phần này, chúng ta sẽ tìm hiểu ở phần sau.

Data model

Project này chúng ta sẽ cùng thực hành xoay quanh model User. Bên trong file prisma/prisma.schema, thêm 1 model mới như sau: // prisma/schema.prisma

model User {
  id          Int      @id @default(autoincrement())
  name        String    
  email       String   @unique
  address     String?
  isActive    Boolean  @default(false)
  createdAt   DateTime @default(now())
  updatedAt   DateTime @updatedAt
}

Ở đây, ta đã tạo một model User với một số trường. Mỗi trường có một tên (id, name, v.v.), một kiểu dữ liệu (Int, String,v.v.) và các thuộc tính tùy chọn khác (@id, @unique, v.v.). Các trường có thể được đặt tùy chọn bằng cách thêm ? sau kiểu dữ liệu.

Trường id có một thuộc tính đặc biệt gọi là@id: khóa chính, @default(autoincrement()): sẽ được tự động tăng và gán cho bất kỳ bản ghi mới được tạo nào.

Hai trường DateTimecreateAtupdateAt, trong đó, thuộc tính @updatedAt sẽ tự động cập nhật bất cứ khi nào bản ghi được sửa đổi.

Migrate database

Với Prisma, ta sẽ chạy migrate để tạo các bảng trong cơ sở dữ liệu. Để tạo và migation đầu tiên, hãy chạy lệnh sau:

npx prisma migrate dev --name "init"

Lệnh này sẽ thực hiện các việc sau:

  • Lưu migration:Prisma Migrate sẽ chụp nhanh schema và tìm ra các lệnh SQL cần thiết để thực hiện migrate. Prisma sẽ lưu file migration chứa các lệnh SQL vào thư mục prisma/migrations mới được tạo.

  • Thực thi migration: Prisma Migrate sẽ thực thi SQL trong file migration để tạo các bảng trong db.

  • Generate Prisma Client: Prisma sẽ generate Prisma Client dựa trên schema mới nhất. Nếu chưa cài đặt thư viện Client, CLI cũng sẽ cài đặt nó. Bạn sẽ thấy gói @prisma/client bên trong package.json. Prisma Client là trình tạo query TypeScript được tạo tự động từ Prisma schema. Nó được điều chỉnh cho phù hợp Prisma schema hiện tại và sẽ được sử dụng để query đến db.

Sau khi hoàn thành, nó sữ trả về messages như sau:

image.png

Kiểm tra file migration đưọc tạo để biết Prisma Migrate đã làm những gì:

-- CreateTable
CREATE TABLE "User" (
    "id" SERIAL NOT NULL,
    "name" TEXT NOT NULL,
    "email" TEXT,
    "address" TEXT,
    "isActive" BOOLEAN NOT NULL DEFAULT false,
    "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
    "updatedAt" TIMESTAMP(3) NOT NULL,

    CONSTRAINT "User_pkey" PRIMARY KEY ("id")
);

Ta thấy đây là SQL để tạo bảng User bên trong PostgreSQL. Nó được Prisma tạo tự động và thực thi.

Seed database

Hiện tại, database đang không có dữ liệu nào. Vì vậy, ta có thể sử dụng seed để tạo dữ liệu mẫu. Đầu tiên, tạo file prisma/seed.ts.

touch prisma/seed.ts

Sau đó, thêm đoạn code sau:

// prisma/seed.ts

import { PrismaClient } from '@prisma/client';

const prisma = new PrismaClient();
async function main() {
  const user1 = await prisma.user.upsert({
    where: { email: 'nguyenvana@prisma.io' },
    update: {},
    create: {
      email: 'nguyenvana@prisma.io',
      name: 'Nguyen Van A',
      address: 'Ha Noi',
    },
  });
  const user2 = await prisma.user.upsert({
    where: { email: 'nguyenvanb@prisma.io' },
    update: {},
    create: {
      email: 'nguyenvanb@prisma.io',
      name: 'Nguyen Van B',
      isActive: true,
    },
  });
  console.log({ user1, user2 });
}
main()
  .then(async () => {
    await prisma.$disconnect();
  })
  .catch(async (e) => {
    console.error(e);
    await prisma.$disconnect();
    process.exit(1);
  });

Ta tạo hai users bằng hàmprisma.upsert(). Hàm upsert sẽ chỉ tạo một bản ghi mới nếu không có bản ghi nào phù hợp với điều kiện trong where. Ta sử dụng upsert thay vì truy vấn createupsert loại bỏ các lỗi liên quan đến việc vô tình thêm cùng một bản ghi hai lần.

Bạn cần cho Prisma biết tập lệnh nào sẽ thực thi khi chạy seeding. Có thể thực hiện việc này bằng cách thêm prisma.seed vào cuối file package.json:

// package.json

// ...
  "scripts": {
    // ...
  },
  "dependencies": {
    // ...
  },
  "devDependencies": {
    // ...
  },
  "jest": {
    // ...
  },
  "prisma": {
    "seed": "ts-node prisma/seed.ts"
  }

Sau đó chỉ việc thực thi thôi:

npx prisma db seed

Ta nhận tạo ra được 2 bản ghi sau:

$ npx prisma db seed
Environment variables loaded from .env
Running seed command `ts-node prisma/seed.ts` ...
{
user1: {
 id: 1,
 name: 'Nguyen Van A',
 email: 'nguyenvana@prisma.io',
 address: 'Ha Noi',
 isActive: false,
 createdAt: 2024-04-23T14:05:21.618Z,
 updatedAt: 2024-04-23T14:05:21.618Z
},
user2: {
 id: 2,
 name: 'Nguyen Van B',
 email: 'nguyenvanb@prisma.io',
 address: null,
 isActive: true,
 createdAt: 2024-04-23T14:05:21.625Z,
 updatedAt: 2024-04-23T14:05:21.625Z
}
}

Tạo Prisma service

Trong NestJS, cách tốt nhất là loại bỏ Prisma Client API trong app. Để thực hiện việc này, tạo một dịch vụ chứa Prisma Client. Service này được gọi là PrismaService, sẽ chịu trách nhiệm khởi tạo PrismaClient và kết nối với CSDL.

Nest CLI cung cấp một cách dễ dàng để generate modulesservices trực tiếp từ CLI. Chạy lệnh sau trong terminal:

npx nest generate module prisma
npx nest generate service prisma
$ npx generate modlue prisma
CREATE src/generate/prismanpx/prismanpx.service.spec.ts (481 bytes)
CREATE src/generate/prismanpx/prismanpx.service.ts (93 bytes)
UPDATE src/app.module.ts (327 bytes)
$ ^C
$ npx nest generate service prisma
CREATE src/prisma/prisma.service.spec.ts (460 bytes)
CREATE src/prisma/prisma.service.ts (90 bytes)
UPDATE src/prisma/prisma.module.ts (163 bytes)
$

Note: Trong một số trường hợp, việc chạy command với server đang chạy có thể dẫn đến việc NestJS đưa ra một exception có nội dung: Error: Cannot find module './app.controller'. Nếu bạn gặp phải lỗi này, hãy chạy lệnh: rm -rf dist và khởi động lại server.

Nó sẽ tạo ra một folder mới ./src/prisma với file prisma.module.tsprisma.service.ts.

// src/prisma/prisma.service.ts

import { INestApplication, Injectable } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';

@Injectable()
export class PrismaService extends PrismaClient {}

Module Prisma sẽ chịu trách nhiệm tạo một singleton của PrismaService và cho phép chia sẻ service trong toàn bộ dự án. Để thực hiện việc này, cần khai báo PrismaService vào export trong prisma.module.ts:

// src/prisma/prisma.module.ts

import { Module } from '@nestjs/common';
import { PrismaService } from './prisma.service';

@Module({
  providers: [PrismaService],
  exports: [PrismaService],
})
export class PrismaModule {}

Giờ đây, bất kỳ module nào imports PrismaModule sẽ có thể sử dụng PrismaService . Đây là pattern phổ biến cho các ứng dụng NestJS.

Sơ kết

Ta đã hoàn tất thiết lập Prisma với việc tạo bảng User và data mẫu để phục vụ cho việc tiếp theo là viết các API CRUD cho User. Mình sẽ trình bày ở phần 2 nhé 😁😁

Cảm ơn vì đã theo dõi bài viết của mình

Hẹn gặp lại mọi người.

image.png


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí