You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

求基于NestJS+Multer+MongoDB的图片上传CRUD代码示例

NestJS + Multer + MongoDB 图片上传CRUD实现

1. 依赖安装

先安装所需依赖包:

npm install @nestjs/mongoose mongoose multer @types/multer

2. 定义MongoDB媒体文件实体

创建src/media/media-file.schema.ts,定义存储图片的MongoDB Schema:

import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { Document } from 'mongoose';

export type MediaFileDocument = MediaFile & Document;

@Schema()
export class MediaFile {
  @Prop({ required: true })
  filename: string;

  @Prop({ required: true })
  mimetype: string;

  @Prop({ required: true, type: Buffer })
  data: Buffer;

  @Prop({ required: true, enum: ['user', 'post'] })
  ownerType: string;

  @Prop({ required: true })
  ownerId: string;
}

export const MediaFileSchema = SchemaFactory.createForClass(MediaFile);

3. Multer配置

在src/media/media.module.ts中配置Multer模块,使用内存存储(直接将文件转为Buffer存入DB),并限制文件类型和大小:

import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';
import { MulterModule } from '@nestjs/platform-express';
import { memoryStorage } from 'multer';
import { MediaFile, MediaFileSchema } from './media-file.schema';
import { MediaService } from './media.service';
import { MediaController } from './media.controller';

@Module({
  imports: [
    MongooseModule.forFeature([{ name: MediaFile.name, schema: MediaFileSchema }]),
    MulterModule.register({
      storage: memoryStorage(),
      limits: {
        fileSize: 5 * 1024 * 1024, // 限制最大5MB
      },
      fileFilter: (req, file, cb) => {
        if (!file.mimetype.startsWith('image/')) {
          return cb(new Error('仅允许上传图片文件'), false);
        }
        cb(null, true);
      },
    }),
  ],
  providers: [MediaService],
  controllers: [MediaController],
  exports: [MediaService],
})
export class MediaModule {}

记得在AppModule中导入MediaModule和Mongoose连接配置:

import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';
import { MediaModule } from './media/media.module';

@Module({
  imports: [
    MongooseModule.forRoot('mongodb://localhost:27017/your-db-name'),
    MediaModule,
  ],
})
export class AppModule {}

4. 服务层CRUD实现

创建src/media/media.service.ts,处理图片的存储、查询、更新和删除逻辑:

import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
import { MediaFile, MediaFileDocument } from './media-file.schema';
import { Express } from 'express';

@Injectable()
export class MediaService {
  constructor(@InjectModel(MediaFile.name) private mediaFileModel: Model<MediaFileDocument>) {}

  // 上传并存储图片
  async uploadFile(
    file: Express.Multer.File,
    ownerType: 'user' | 'post',
    ownerId: string,
  ): Promise<MediaFile> {
    const mediaFile = new this.mediaFileModel({
      filename: file.originalname,
      mimetype: file.mimetype,
      data: file.buffer,
      ownerType,
      ownerId,
    });
    return mediaFile.save();
  }

  // 根据ID查询图片
  async getFileById(id: string): Promise<MediaFile> {
    const mediaFile = await this.mediaFileModel.findById(id).exec();
    if (!mediaFile) {
      throw new NotFoundException('媒体文件不存在');
    }
    return mediaFile;
  }

  // 根据所有者类型和ID查询图片(如用户头像、帖子横幅)
  async getFileByOwner(ownerType: 'user' | 'post', ownerId: string): Promise<MediaFile | null> {
    return this.mediaFileModel.findOne({ ownerType, ownerId }).exec();
  }

  // 更新图片(替换现有文件)
  async updateFile(
    id: string,
    file: Express.Multer.File,
  ): Promise<MediaFile> {
    const updatedFile = await this.mediaFileModel.findByIdAndUpdate(
      id,
      { filename: file.originalname, mimetype: file.mimetype, data: file.buffer },
      { new: true },
    ).exec();
    if (!updatedFile) {
      throw new NotFoundException('媒体文件不存在');
    }
    return updatedFile;
  }

  // 删除图片
  async deleteFile(id: string): Promise<void> {
    const result = await this.mediaFileModel.findByIdAndDelete(id).exec();
    if (!result) {
      throw new NotFoundException('媒体文件不存在');
    }
  }
}

5. 控制器层API实现

创建src/media/media.controller.ts,对外暴露CRUD接口:

import { Controller, Post, Get, Put, Delete, Param, Body, UploadedFile, UseInterceptors, Res, HttpStatus } from '@nestjs/common';
import { FileInterceptor } from '@nestjs/platform-express';
import { MediaService } from './media.service';
import { Response } from 'express';

@Controller('media')
export class MediaController {
  constructor(private readonly mediaService: MediaService) {}

  // 上传图片:POST /media/upload
  @Post('upload')
  @UseInterceptors(FileInterceptor('file'))
  async uploadFile(
    @UploadedFile() file: Express.Multer.File,
    @Body('ownerType') ownerType: 'user' | 'post',
    @Body('ownerId') ownerId: string,
  ) {
    const mediaFile = await this.mediaService.uploadFile(file, ownerType, ownerId);
    return {
      id: mediaFile._id,
      filename: mediaFile.filename,
      mimetype: mediaFile.mimetype,
      ownerType: mediaFile.ownerType,
      ownerId: mediaFile.ownerId,
    };
  }

  // 根据ID获取图片:GET /media/:id
  @Get(':id')
  async getFile(@Param('id') id: string, @Res() res: Response) {
    const mediaFile = await this.mediaService.getFileById(id);
    res.setHeader('Content-Type', mediaFile.mimetype);
    res.send(mediaFile.data);
  }

  // 根据所有者获取图片:GET /media/owner/:ownerType/:ownerId
  @Get('owner/:ownerType/:ownerId')
  async getFileByOwner(@Param('ownerType') ownerType: 'user' | 'post', @Param('ownerId') ownerId: string, @Res() res: Response) {
    const mediaFile = await this.mediaService.getFileByOwner(ownerType, ownerId);
    if (!mediaFile) {
      res.status(HttpStatus.NOT_FOUND).send('图片不存在');
      return;
    }
    res.setHeader('Content-Type', mediaFile.mimetype);
    res.send(mediaFile.data);
  }

  // 更新图片:PUT /media/:id
  @Put(':id')
  @UseInterceptors(FileInterceptor('file'))
  async updateFile(@Param('id') id: string, @UploadedFile() file: Express.Multer.File) {
    const updatedFile = await this.mediaService.updateFile(id, file);
    return {
      id: updatedFile._id,
      filename: updatedFile.filename,
      mimetype: updatedFile.mimetype,
      ownerType: updatedFile.ownerType,
      ownerId: updatedFile.ownerId,
    };
  }

  // 删除图片:DELETE /media/:id
  @Delete(':id')
  async deleteFile(@Param('id') id: string) {
    await this.mediaService.deleteFile(id);
    return { message: '图片删除成功' };
  }
}

6. 使用说明

  • 上传图片:发送POST请求到/media/upload,使用FormData格式,包含file(图片文件)、ownerType(可选值user/post)、ownerId(用户或帖子ID)
  • 查看图片:访问/media/:id可直接获取图片内容,浏览器会自动渲染;访问/media/owner/:ownerType/:ownerId可获取指定用户/帖子的图片
  • 更新图片:发送PUT请求到/media/:id,FormData包含新的file文件
  • 删除图片:发送DELETE请求到/media/:id删除指定图片

注意事项

  • MongoDB存储二进制文件适合小体积图片(如头像、横幅),大文件建议使用云存储服务(如S3)
  • 可扩展添加所有者ID合法性验证(关联用户/帖子是否存在)
  • 可集成sharp库实现图片压缩、格式转换等优化操作

内容的提问来源于stack exchange,提问作者Julio Raposo

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.13 01:45:34