You need to enable JavaScript to run this app. 如何在NestJS中通过Mongoose实现上传文件存储到MongoDB
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.31 14:15:04