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:
- Đầu tiên, tạo một dự án NestJS mới.
- Sau đó, khởi tạo máy chủ PostgreSQL và kết nối với nó bằng Prisma.
- 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 DateTime
là createAt
và updateAt
, 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 nhanhschema
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ụcprisma/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 queryTypeScript
đượ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:
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 create
vì upsert
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 modules và services 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.ts
và prisma.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.
All rights reserved