Express API多集合CRUD:单路由通用控制器or多路由独立实现?
优先选择通用控制器方案
对于你的场景,编写可接收集合参数的通用控制器是更优的选择,不管是从代码体积还是性能角度,都比重复复制函数要好,具体分析和实现如下:
为什么选通用控制器?
- 代码体积:避免了大量重复的CRUD逻辑代码,只需要维护一套核心函数,新增集合时仅需配置路由(甚至无需修改路由),大幅减少冗余代码,降低应用体积。
- 性能:通用控制器仅将集合名作为参数传递给MongoDB驱动,本质上与针对单个集合编写的函数执行逻辑完全一致,不会产生额外性能开销——MongoDB的操作性能只与查询本身、索引有关,和是否使用通用函数无关。
- 可维护性:后续若要修改CRUD逻辑(比如统一添加日志、统一处理错误),只需修改一处代码,不用逐个调整每个集合的函数,维护成本极低。
通用控制器实现示例
1. 通用核心控制器(genericController.js)
const { MongoClient, ObjectId } = require('mongodb'); const dbPromise = MongoClient.connect('mongodb://localhost:27017/your-database-name') .then(client => client.db()); // 允许访问的集合白名单,防止恶意请求 const ALLOWED_COLLECTIONS = ['Customers', 'Products', 'Suppliers']; // 通用插入文档 exports.insert = async (req, res) => { const { collectionName } = req.params; const docData = req.body; // 校验集合是否合法 if (!ALLOWED_COLLECTIONS.includes(collectionName)) { return res.status(403).json({ error: '无权访问该集合' }); } try { const db = await dbPromise; const result = await db.collection(collectionName).insertOne(docData); res.status(201).json({ insertedId: result.insertedId }); } catch (err) { res.status(500).json({ error: err.message }); } }; // 通用根据ID查询单个文档 exports.findOne = async (req, res) => { const { collectionName, id } = req.params; if (!ALLOWED_COLLECTIONS.includes(collectionName)) { return res.status(403).json({ error: '无权访问该集合' }); } try { const db = await dbPromise; const doc = await db.collection(collectionName).findOne({ _id: new ObjectId(id) }); if (!doc) { return res.status(404).json({ error: '文档不存在' }); } res.json(doc); } catch (err) { res.status(500).json({ error: err.message }); } }; // 通用条件查询文档 exports.find = async (req, res) => { const { collectionName } = req.params; let query = { ...req.query }; if (!ALLOWED_COLLECTIONS.includes(collectionName)) { return res.status(403).json({ error: '无权访问该集合' }); } // 转换查询参数类型(比如把字符串数字转为Number) Object.keys(query).forEach(key => { if (!isNaN(query[key])) { query[key] = Number(query[key]); } }); try { const db = await dbPromise; const docs = await db.collection(collectionName).find(query).toArray(); res.json(docs); } catch (err) { res.status(500).json({ error: err.message }); } };
2. 路由配置(routes.js)
const express = require('express'); const router = express.Router(); const genericController = require('./genericController'); // 绑定通用控制器到路由 router.post('/:collectionName', genericController.insert); router.get('/:collectionName/:id', genericController.findOne); router.get('/:collectionName', genericController.find); module.exports = router;
补充优化点
如果后续需要针对不同集合做差异化处理,也可以在通用控制器基础上扩展:
- 数据验证:针对不同集合定义验证规则,在插入前校验数据格式,比如用Joi库:
const Joi = require('joi'); const VALIDATION_SCHEMAS = { Customers: Joi.object({ name: Joi.string().required(), email: Joi.string().email().required() }), Products: Joi.object({ name: Joi.string().required(), price: Joi.number().positive().required() }) }; // 在insert函数中添加验证 const schema = VALIDATION_SCHEMAS[collectionName]; if (schema) { const { error } = schema.validate(docData); if (error) { return res.status(400).json({ error: error.details[0].message }); } } - 分页与排序:在通用
find函数中支持分页、排序参数,提升查询灵活性:const { page = 1, limit = 10, sortBy = '_id', sortOrder = 'asc' } = req.query; const skip = (page - 1) * limit; const sortOptions = { [sortBy]: sortOrder === 'desc' ? -1 : 1 }; const docs = await db.collection(collectionName) .find(query) .sort(sortOptions) .skip(skip) .limit(Number(limit)) .toArray();
内容的提问来源于stack exchange,提问作者XxTavo16xX
相关产品推荐
相关产品推荐

