求基于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
相关产品推荐
相关产品推荐

