如何在NestJS中通过Mongoose实现上传文件存储到MongoDB
NestJS + Mongoose 存储文件到MongoDB的可落地方案
核心注意点:不要直接把文件Buffer存在普通文档Schema里,MongoDB单文档硬限制16M,超过直接写入失败,生产环境统一通过GridFS能力对接,和现有Mongoose连接完全复用,不需要额外搭建存储服务。
前置准备
你已经具备的基础不用重复配置,只需要补装对应的依赖:
- 安装GridFS核心类型包:
npm install mongoose-gridfs @types/mongoose-gridfs - 确认AppModule里已经正常初始化了
MongooseModule.forRoot的连接逻辑
第一步:定义GridFS元数据Schema
GridFS会自动把大文件拆成固定大小的分片存在fs.chunks集合,文件本身的元数据存在fs.files集合,我们只需要把元数据集合映射成Mongoose模型,不用手动管理分片逻辑:
// src/file/schemas/uploaded-file.schema.ts import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; import { GridFSBucket, ObjectId } from 'mongodb'; import * as mongoose from 'mongoose'; @Schema({ collection: 'fs.files' }) export class UploadedFile { @Prop() filename: string; @Prop() length: number; @Prop() chunkSize: number; @Prop() uploadDate: Date; @Prop() contentType: string; @Prop({ type: ObjectId }) _id: ObjectId; } export const UploadedFileSchema = SchemaFactory.createForClass(UploadedFile); // 复用现有Mongoose连接生成GridFS操作实例 export const getGridFSBucket = () => { const connection = mongoose.connection; return new GridFSBucket(connection.db, { bucketName: 'fs' }); };
第二步:封装文件读写的Service逻辑
你已经能通过@Post接口拿到multer解析后的上传文件对象(结构为Express.Multer.File),直接在Service层对接GridFS接口完成读写:
// src/file/file.service.ts import { Injectable } from '@nestjs/common'; import { InjectModel } from '@nestjs/mongoose'; import { Model } from 'mongoose'; import { UploadedFile, getGridFSBucket } from './schemas/uploaded-file.schema'; @Injectable() export class FileService { constructor( @InjectModel(UploadedFile.name) private uploadedFileModel: Model<UploadedFile>, ) {} // 把接收到的文件写入MongoDB async writeFileToMongo(file: Express.Multer.File) { const bucket = getGridFSBucket(); const uploadStream = bucket.openUploadStream(file.originalname, { contentType: file.mimetype, }); // 直接把multer解析得到的文件Buffer写入上传流 uploadStream.end(file.buffer); return new Promise((resolve, reject) => { uploadStream.on('finish', () => { resolve({ fileId: uploadStream.id.toString(), filename: file.originalname, size: file.size, }); }); uploadStream.on('error', (err) => reject(err)); }); } // 根据文件ID读取MongoDB里的文件流,供@Get接口使用 async readFileFromMongo(fileId: string) { const bucket = getGridFSBucket(); return bucket.openDownloadStream(new mongoose.Types.ObjectId(fileId)); } // 查询文件元数据 async getFileMeta(fileId: string) { return this.uploadedFileModel.findById(fileId).lean(); } }
第三步:对接现有Controller接口
对接现有@Post上传接口
不需要改动你已经写好的上传接口结构,只需要把接口拿到的文件对象传给刚才封装的writeFileToMongo方法即可。避坑提示:确保你现有multer配置用的是内存存储(storage: memoryStorage()),这样拿到的文件自带完整Buffer,可以直接写入MongoDB,不需要做本地临时文件的IO读取。
对接现有@Get响应接口
在你原来的@Get接口里,调用readFileFromMongo拿到文件流后,结合查询到的元数据设置响应头,把流pipe到Express的response对象即可,示例片段:
// src/file/file.controller.ts 接口代码片段 @Get('file/:fileId') async getFile(@Param('fileId') fileId: string, @Res() res: Response) { const fileStream = await this.fileService.readFileFromMongo(fileId); const fileMeta = await this.fileService.getFileMeta(fileId); res.set({ 'Content-Type': fileMeta.contentType, 'Content-Disposition': `inline; filename="${encodeURIComponent(fileMeta.filename)}"`, }); fileStream.pipe(res); }
常见踩坑点
- 不要为了存文件给普通Schema加Buffer字段,16M的文档限制是MongoDB的硬规则,超过大小会直接写入报错
- GridFS不需要额外的数据库连接,所有操作都复用你已经配置好的Mongoose连接,不会产生额外的连接开销
- 文件读写的流不需要手动做分片、拼接处理,GridFS会自动完成分片存储、读取时的流拼接
内容的提问来源于stack exchange,提问作者John Patrick Kokar
相关产品推荐
相关产品推荐

