Cách tích hợp MongoDB với NestJS sử dụng Mongoose
- Published on
MongoDB là một cơ sở dữ liệu NoSQL phổ biến, và kết hợp nó với NestJS giúp xây dựng các ứng dụng nhanh chóng và hiệu quả. Trong bài viết này, chúng ta sẽ tìm hiểu cách tích hợp MongoDB vào một dự án NestJS sử dụng Mongoose.
Nội dung
- Cấu trúc của dự án demo
- 1. Cài Đặt và Cấu Hình
- 2. Định Nghĩa Schema
- 3. Tạo Module
- 4. Implement Service
- 6. Data Transfer Objects (DTOs)
- 7. Relationships và Populate
- 8. Best Practices
- Tổng kết
Cấu trúc của project demo
Dưới đây là cấu trúc thư mục của project demo, giúp tổ chức mã nguồn một cách rõ ràng:
nest-mongodb-demo/
├── src/
│ ├── modules/
│ │ └── articles/ # Demo module
│ │ ├── dto/
│ │ │ ├── create-article.dto.ts
│ │ │ ├── search-article.dto.ts
│ │ │ └── update-article.dto.ts
│ │ ├── schemas/
│ │ │ └── article.schema.ts
│ │ ├── articles.controller.ts
│ │ ├── articles.service.ts
│ │ └── articles.module.ts
│ ├── app.module.ts
│ └── main.ts
├── .env # MongoDB connection config
└── package.json
Lưu ý:
MongoDB: Cơ sở dữ liệu NoSQL phổ biến.
Mongoose: Thư viện ODM (Object Data Modeling) cho MongoDB tương thích tốt với NestJS.
1. Cài Đặt và Cấu Hình
1.1. Cài đặt các package cần thiết Để sử dụng MongoDB trong NestJS, bạn cần cài đặt thư viện @nestjs/mongoose và mongoose:
npm install @nestjs/mongoose mongoose
1.2. Tạo file .env
và cấu hình MongoDB Tạo một file .env ở thư mục gốc của dự án để lưu thông tin kết nối:
MONGODB_URI=mongodb://localhost:27017/nest-mongodb-demo
1.3. Cấu hình MongoDB trong AppModule Bạn cần chỉnh sửa file app.module.ts để sử dụng MongooseModule và đọc thông tin từ file .env:
// src/app.module.ts
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { MongooseModule } from '@nestjs/mongoose';
import { ArticlesModule } from './modules/articles/articles.module';
@Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
envFilePath: [`.env.${process.env.NODE_ENV || 'dev'}`, '.env'],
}),
MongooseModule.forRootAsync({
useFactory: async () => ({
uri: process.env.MONGODB_URI,
}),
}),
ArticlesModule,
],
})
export class AppModule {}
2. Định Nghĩa Schema
Schema xác định cấu trúc tài liệu trong MongoDB. NestJS sử dụng các decorator để định nghĩa schema một cách rõ ràng.
Ví dụ Schema cho Article
// src/modules/articles/schemas/article.schema.ts
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { HydratedDocument } from 'mongoose';
import { Schema as MongooseSchema } from 'mongoose';
import { Category } from './category.schema'; // Giả sử bạn có category schema riêng
@Schema()
export class Article {
@Prop({ required: true, maxlength: 255 })
title: string;
@Prop({ required: true })
content: string;
@Prop({ maxlength: 1000 })
description: string;
@Prop()
thumbnail: string;
@Prop({ default: 0 })
numberOfView: number;
@Prop({
type: [{ type: MongooseSchema.Types.ObjectId, ref: 'Category' }],
})
categories: Category[];
}
export type ArticleDocument = HydratedDocument<Article>;
export const ArticleSchema = SchemaFactory.createForClass(Article);
Giải thích:
- @Schema(): Đánh dấu một class là schema của MongoDB.
- @Prop(): Định nghĩa một thuộc tính của tài liệu.
- SchemaFactory.createForClass: Tạo schema dựa trên class.
3. Tạo Module
Mỗi tài nguyên trong ứng dụng NestJS thường được quản lý bởi một module.
// src/modules/articles/articles.module.ts
import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';
import { ArticlesController } from './articles.controller';
import { ArticlesService } from './articles.service';
import { Article, ArticleSchema } from './schemas/article.schema';
@Module({
imports: [
MongooseModule.forFeature([
{ name: Article.name, schema: ArticleSchema },
]),
],
controllers: [ArticlesController],
providers: [ArticlesService],
exports: [ArticlesService],
})
export class ArticlesModule {}
Lưu ý:
MongooseModule.forFeature: Đăng ký schema để sử dụng trong module này.
4. Implement Service
Service chịu trách nhiệm xử lý logic của ứng dụng và giao tiếp với MongoDB thông qua model.
4.1. CRUD Operations
// src/modules/articles/articles.service.ts
import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
import { Article, ArticleDocument } from './schemas/article.schema';
import { CreateArticleDto } from './dto/create-article.dto';
import { UpdateArticleDto } from './dto/update-article.dto';
import { SearchArticleDto } from './dto/search-article.dto';
@Injectable()
export class ArticlesService {
constructor(
@InjectModel(Article.name) private articleModel: Model<ArticleDocument>,
) {}
// Create
async create(createArticleDto: CreateArticleDto): Promise<Article> {
const createdArticle = new this.articleModel(createArticleDto);
return createdArticle.save();
}
// Read
async findAll(searchDto: SearchArticleDto): Promise<Article[]> {
// Ví dụ bạn có thể tùy biến prepareQuery(searchDto)
const query: any = {};
if (searchDto.title) {
query.title = { $regex: searchDto.title, $options: 'i' };
}
return this.articleModel
.find(query)
.sort({ _id: -1 })
.populate('categories')
.exec();
}
// Update
async update(id: string, updateArticleDto: UpdateArticleDto): Promise<Article> {
return this.articleModel
.findByIdAndUpdate(id, updateArticleDto, { new: true })
.exec();
}
// Delete
async delete(id: string): Promise<Article> {
return this.articleModel.findByIdAndDelete(id).exec();
}
}
Giải thích:
- InjectModel: Inject model để thao tác với MongoDB.
- create, findAll, update, delete: Các phương thức cơ bản để thao tác với MongoDB.
5. Implement Controller
Controller chịu trách nhiệm nhận yêu cầu từ client và chuyển chúng đến service để xử lý.
// src/modules/articles/articles.controller.ts
import { Controller, Get, Post, Put, Delete, Body, Param, Query } from '@nestjs/common';
import { ArticlesService } from './articles.service';
import { CreateArticleDto } from './dto/create-article.dto';
import { UpdateArticleDto } from './dto/update-article.dto';
import { SearchArticleDto } from './dto/search-article.dto';
import { Article } from './schemas/article.schema';
@Controller('articles')
export class ArticlesController {
constructor(private readonly articlesService: ArticlesService) {}
@Post()
async create(@Body() createArticleDto: CreateArticleDto): Promise<Article> {
return this.articlesService.create(createArticleDto);
}
@Get()
async findAll(@Query() searchDto: SearchArticleDto): Promise<Article[]> {
return this.articlesService.findAll(searchDto);
}
@Put(':id')
async update(
@Param('id') id: string,
@Body() updateArticleDto: UpdateArticleDto,
): Promise<Article> {
return this.articlesService.update(id, updateArticleDto);
}
@Delete(':id')
async delete(@Param('id') id: string): Promise<Article> {
return this.articlesService.delete(id);
}
}
6. Data Transfer Objects (DTOs)
DTOs được sử dụng để validate dữ liệu đầu vào và đầu ra. Sử dụng thư viện class-validator và class-transformer để validate dữ liệu.
// src/modules/articles/dto/create-article.dto.ts
import { IsNotEmpty, IsString, MaxLength, IsOptional, IsArray } from 'class-validator';
export class CreateArticleDto {
@IsNotEmpty()
@IsString()
@MaxLength(255)
title: string;
@IsNotEmpty()
@IsString()
content: string;
@IsOptional()
@IsString()
@MaxLength(1000)
description?: string;
@IsOptional()
@IsArray()
@IsString({ each: true })
categories?: string[];
}
7. Relationships và Populate
Mongoose hỗ trợ các loại relationships khác nhau, bao gồm one-to-many, many-to-many, và one-to-one. Dưới đây là một ví dụ về one-to-many relationship.
7.1. One-to-Many Relationship
// src/modules/articles/schemas/article.schema.ts
@Prop({
type: [{ type: MongooseSchema.Types.ObjectId, ref: 'Category' }],
})
categories: Category[];
7.2. Populate Related Data
// src/modules/articles/articles.service.ts
const article = await this.articleModel
.findById(id)
.populate('categories')
.exec();
Populate một số trường của schema khác
// src/modules/articles/articles.service.ts
const article = await this.articleModel
.findById(id)
.populate({
path: 'categories',
select: 'name description',
})
.exec();
8. Best Practices
Một số best practices khi sử dụng Mongoose với NestJS:
- Sử dụng TypeScript Decorators
- Leverage @Prop(), @Schema() cho MongoDB schemas
- Sử dụng validation decorators cho DTOs
- Implement Pagination
- Luôn có limit và skip cho queries lớn
- Trả về metadata với kết quả
- Error Handling
- Sử dụng custom exceptions
- Implement global exception filters
- Type Safety
- Định nghĩa interfaces và types
- Sử dụng DTOs cho data validation
- Performance
- Index các fields thường query
- Sử dụng projection để select specific fields
- Implement caching khi cần thiết
Tổng kết
Bài viết đã giới thiệu cách tích hợp MongoDB vào một dự án NestJS, bao gồm:
- Cài đặt và cấu hình Mongoose
- Định nghĩa schema
- Tạo module, implement service và controller
- Sử dụng DTOs để validate dữ liệu
- Sử dụng relationships, populate
- Và một số “Best Practices” quan trọng
Hy vọng với hướng dẫn này, bạn có thể nhanh chóng xây dựng và triển khai các tính năng phức tạp hơn trong dự án NestJS + MongoDB. Chúc bạn thành công!