如何使用Apollo-server-express与MongoDB上传单张/多张图片?
用 Apollo Server Express + MongoDB 实现单/多张图片上传
1. 安装依赖
先安装所需的核心包:
npm install apollo-server-express express mongoose graphql-upload graphql uuid
2. 服务器基础配置
初始化 Express 并集成 Apollo Server,同时配置文件上传中间件:
const express = require('express'); const { ApolloServer } = require('apollo-server-express'); const { graphqlUploadExpress } = require('graphql-upload'); const mongoose = require('mongoose'); const typeDefs = require('./schema'); const resolvers = require('./resolvers'); const app = express(); // 配置文件上传限制(示例:单文件最大10MB) app.use(graphqlUploadExpress({ maxFileSize: 10 * 1024 * 1024 })); // 连接 MongoDB mongoose.connect('mongodb://localhost:27017/image-upload-db') .then(() => console.log('MongoDB 连接成功')) .catch(err => console.error('MongoDB 连接失败:', err)); // 创建 Apollo Server 实例 const server = new ApolloServer({ typeDefs, resolvers, csrfPrevention: false, cache: 'bounded', }); // 启动服务 async function startServer() { await server.start(); server.applyMiddleware({ app }); app.listen({ port: 4000 }, () => { console.log(`🚀 服务器运行在 http://localhost:4000${server.graphqlPath}`); }); } startServer();
3. MongoDB 模型设计
创建两个模型:一个用于单张图片存储,一个用于关联多张图片的帖子(也可合并为单个模型,用数组存多张图片)。
单张图片模型 (models/SingleImage.js)
const mongoose = require('mongoose'); const singleImageSchema = new mongoose.Schema({ filename: { type: String, required: true, unique: true }, originalName: { type: String, required: true }, mimeType: { type: String, required: true }, path: { type: String, required: true }, // 本地存储路径或 GridFS ID size: { type: Number, required: true }, createdAt: { type: Date, default: Date.now } }); module.exports = mongoose.model('SingleImage', singleImageSchema);
多图关联帖子模型 (models/MultiImagePost.js)
const mongoose = require('mongoose'); const multiImagePostSchema = new mongoose.Schema({ title: { type: String, required: true }, images: [{ filename: { type: String, required: true }, originalName: { type: String, required: true }, mimeType: { type: String, required: true }, path: { type: String, required: true }, size: { type: Number, required: true } }], createdAt: { type: Date, default: Date.now } }); module.exports = mongoose.model('MultiImagePost', multiImagePostSchema);
4. GraphQL Schema 定义
创建 schema.js,定义上传标量和操作类型:
const { gql } = require('apollo-server-express'); const typeDefs = gql` scalar Upload type SingleImage { id: ID! filename: String! originalName: String! mimeType: String! path: String! size: Int! createdAt: String! } type MultiImagePost { id: ID! title: String! images: [SingleImage!]! createdAt: String! } type Mutation { uploadSingleImage(file: Upload!): SingleImage! uploadMultipleImages(title: String!, files: [Upload!]!): MultiImagePost! } `; module.exports = typeDefs;
5. Resolver 核心逻辑
创建 resolvers.js,处理文件存储和数据库写入:
const { createWriteStream, existsSync, mkdirSync, statSync } = require('fs'); const { join } = require('path'); const { v4: uuidv4 } = require('uuid'); const SingleImage = require('./models/SingleImage'); const MultiImagePost = require('./models/MultiImagePost'); // 确保上传目录存在 const uploadDir = join(__dirname, 'uploads'); if (!existsSync(uploadDir)) mkdirSync(uploadDir); const resolvers = { Upload: require('graphql-upload').GraphQLUpload, Mutation: { // 单张图片上传 uploadSingleImage: async (_, { file }) => { const { createReadStream, filename, mimetype } = await file; const uniqueFilename = `${uuidv4()}-${filename}`; const filePath = join(uploadDir, uniqueFilename); // 写入本地文件 await new Promise((resolve, reject) => { createReadStream() .pipe(createWriteStream(filePath)) .on('finish', resolve) .on('error', reject); }); // 保存到 MongoDB const image = new SingleImage({ filename: uniqueFilename, originalName: filename, mimeType: mimetype, path: filePath, size: statSync(filePath).size }); await image.save(); return image; }, // 多张图片上传 uploadMultipleImages: async (_, { title, files }) => { const images = []; for (const file of files) { const { createReadStream, filename, mimetype } = await file; const uniqueFilename = `${uuidv4()}-${filename}`; const filePath = join(uploadDir, uniqueFilename); await new Promise((resolve, reject) => { createReadStream() .pipe(createWriteStream(filePath)) .on('finish', resolve) .on('error', reject); }); images.push({ filename: uniqueFilename, originalName: filename, mimeType: mimetype, path: filePath, size: statSync(filePath).size }); } // 创建帖子并关联图片 const post = new MultiImagePost({ title, images }); await post.save(); return post; } } }; module.exports = resolvers;
6. 优化建议
- 文件类型校验:在 resolver 中检查
mimetype,仅允许image/jpeg、image/png等合法图片类型。 - GridFS 适配:若需存储超过16MB的大图片,改用 MongoDB GridFS 替代本地存储,规避单文档大小限制。
- 静态文件路由:添加 Express 静态路由,让前端可直接访问图片:
app.use('/uploads', express.static(uploadDir)); - 异常处理:在 resolver 中添加 try-catch 块,处理文件写入失败、数据库存储异常等情况。
7. 测试上传
启动服务器后,打开 GraphQL Playground(http://localhost:4000/graphql),用以下 mutation 测试:
单张上传
mutation { uploadSingleImage(file: $file) { id originalName mimeType size } }
在 Playground 底部的「QUERY VARIABLES」区域选择文件,变量名填 file。
多张上传
mutation { uploadMultipleImages(title: "旅行相册", files: $files) { id title images { originalName size } } }
同样在变量区域选择多个文件,变量名填 files。
内容的提问来源于stack exchange,提问作者dmx
相关产品推荐
相关产品推荐

