在MongoDB中的位置
关系型数据库用表来组织行记录,MongoDB则用来组织文档。不强制固定字段结构,同一内的文档可以拥有不同字段,这给迭代期的数据模型带来弹性。但弹性不等于随意,命名、创建时机、索引规划仍然需要提前考虑。
createCollection是MongoDB提供的显式建方法。Node.js驱动通过db.createCollection(name,options,callback)调用它。实际项目里,很多人直接插入文档让MongoDB自动建,这确实能跑通,但显式创建的价值在于可以同时指定校验规则、大小上限、排序规则等参数。选择哪种方式,取决于你对数据结构的控制需求。
原始示例的梳理
先看一段基础代码,文件命名为codehh_employees.js:
var MongoClient = require('mongodb').MongoClient;
var url = "mongodb://localhost:27017/MongoDatabase";
MongoClient.connect(url, function(err, db) {
if (err) throw err;
db.createCollection("employees", function(err, res) {
if (err) throw err;
console.log("Collection is created!");
db.close();
});
});
终端执行:
node codehh_employees.js
连接串中的MongoDatabase是数据库名,MongoDB在首次写入数据或创建时会自动建库,不需要单独的createDatabase命令。这一点和MySQL要先CREATEDATABASE不同,刚接触时容易困惑。
上面代码用的是旧版回调式API。db.createCollection在已存在时会报错,错误码为48,即NamespaceExists。生产代码不能直接throwerr了事,需要判断这个错误是否属于可忽略的情况。
显式创建与隐式创建的取舍
向一个不存在的插入文档时,MongoDB会自动创建它。这种隐式方式在原型阶段很方便,但有几个代价:
第一,无法在创建时指定validator。MongoDB支持JSONSchema校验,可以在建时限定字段类型、必填项、取值范围。等已经存在再补校验规则,需要走collMod命令,多一步操作。
第二,无法指定capped、size、max等参数。固定在日志、环形缓冲场景有用,隐式创建拿不到这些能力。
第三,团队协作时,结构缺少显式声明,新成员只能靠读代码或抽样文档去猜。
我的建议是:核心业务一律显式创建,放在项目初始化脚本或迁移脚本里;临时分析、一次性导入的场景可以用隐式创建省事。
现在驱动写法
mongodb驱动4.x之后转向Promise,回调风格逐渐退出。下面是等价写法,文件名用codehh_create_employees.js:
const { MongoClient } = require('mongodb');
const url = 'mongodb://localhost:27017/MongoDatabase';
const client = new MongoClient(url);
async function main() {
try {
await client.connect();
const db = client.db('MongoDatabase');
const exists = await db.listCollections({ name: 'employees' }).hasNext();
if (!exists) {
await db.createCollection('employees', {
validator: {
$jsonSchema: {
bsonType: 'object',
required: ['name', 'department'],
properties: {
name: { bsonType: 'string' },
department: { bsonType: 'string' },
salary: { bsonType: 'double', minimum: 0 }
}
}
}
});
console.log('employees 已创建');
} else {
console.log('employees 已存在,跳过创建');
}
} catch (err) {
console.error('创建失败:', err.message);
} finally {
await client.close();
}
}
main();
这段代码比原始示例多了几个考虑:先检查是否存在,避免重复创建报错;加上JSONSchema校验;用try/catch/finally保证连接释放。listCollections返回游标,hasNext()判断是否有同名。
命名与项目实例
名建议用复数、小写、下划线分隔,比如employees、order_items。避免用system.前缀,那是MongoDB内部保留的。数据库名和名区分大小写,Employees和employees是两个,跨平台协作时容易踩坑。
以一个员工管理系统为例,项目结构可以这样安排:
project/
├── src/
│ ├── db/
│ │ ├── client.js # 连接管理
│ │ └── initCollections.js # 初始化
│ ├── models/
│ │ └── employee.js
│ └── app.js
client.js负责单例连接:
const { MongoClient } = require('mongodb');
let client;
async function getClient() {
if (!client) {
client = new MongoClient('mongodb://localhost:27017/MongoDatabase');
await client.connect();
}
return client;
}
module.exports = { getClient };
initCollections.js集中建:
const { getClient } = require('./client');
async function initCollections() {
const client = await getClient();
const db = client.db('MongoDatabase');
const collections = await db.listCollections().toArray();
const names = collections.map(c => c.name);
if (!names.includes('employees')) {
await db.createCollection('employees');
}
if (!names.includes('departments')) {
await db.createCollection('departments');
}
console.log('初始化完成');
}
initCollections().catch(console.error);
这样应用启动时调用一次,避免每个请求都去建。
错误处理与类型安全
createCollection常见错误有三类:连接失败、权限不足、已存在。连接失败通常是MongoDB服务未启动或URL写错;权限不足出现在开启了认证的实例上,当前用户没有createCollection权限;已存在则是NamespaceExists。
在TypeScript项目中,可以给文档定义接口:
interface Employee {
name: string;
department: string;
salary?: number;
joinedAt?: Date;
}
配合驱动泛型db.collection<Employee>('employees'),插入和查询时能获得字段提示。校验规则和类型定义双管齐下,数据质量更有保障。
本节课程知识要点
-
MongoDB数据存于,
createCollection用于显式创建,插入文档也可隐式创建。 -
连接串中的数据库在首次操作时自动创建,无需单独建库命令。
-
显式创建的优势在于可同时指定validator、capped、size等参数。
-
已存在时创建会报NamespaceExists错误,生产代码应先检查或捕获该错误。
-
现在mongodb驱动推荐Promise/async写法,并复用MongoClient连接。
-
命名建议小写复数、下划线分隔,避开
system.前缀。
练习与思考
-
把原始回调示例改写成async/await版本,并加入存在性检查。
-
给
employees添加JSONSchema校验,要求name为字符串且必填,salary为不小于0的数值。 -
思考:如果两个服务同时启动并都执行
createCollection,可能出现什么情况?如何规避?
createCollection本身方法不复杂,但围绕它的创建时机、参数配置、错误处理和项目组织方式,决定了数据层是否稳固。把初始化从业务代码里抽出来,集中管理,是中小型项目值得采用的做法。后续可以进一步了解索引创建、校验规则修改以及分片的创建方式,这些都会建立在这个基础之上。