使用数据库
数据库的选择
根据实际情况,有很多数据库可以选择, 但我们强烈推荐你使用 支持 JSON 嵌套结构 的 NoSQL 数据库,因为:
- JSON 嵌套结构更利于发挥 TypeScript 类型特性,参考 类型设计
- 大幅简化关系型表结构设计,降低维护成本
- 无需学习 SQL,纯 API 式调用更容易上手,也规避了 SQL 注入等安全风险
例如 MongoDB 就是一个非常不错的选择。
使用 MongoDB
MongoDB 是成熟的 NoSQL 数据库,对 TypeScript 支持十分良好。
MongoDB Atlas 是官方推出的云数据库服务,提供 512 MB 免费空间,非常适合学习使用。
安装
MongoDB 已经提供了官方的 NodeJS 驱动:
npm i mongodb
# 或者
yarn add mongodb
然后就可以在代码中使用了,使用细节可以参阅官方文档。
配置和启动
MongoDB 的客户端都是异步的 API,并且会自动维护连接池,所以你只需要全局创建一个共享的 Db
实例即可,
例如:
import { Db, MongoClient } from "mongodb";
export class Global {
static db: Db;
static async initDb() {
const uri = 'mongodb://username:password@xxx.com:27017/test?authSource=admin';
const client = await new MongoClient(uri).connect();
this.db = client.db();
}
}
安全起见,你可以将连接配置放在配置文件或环境变量中。
你需要在服务启动前就连接好数据库,所以修改一下 index.ts
中的初始化流程:
// Initialize
async function init() {
// Auto implement APIs
await server.autoImplementApi(path.resolve(__dirname, 'api'));
// 在服务启动前先连接好数据库
await Global.initDb();
};
// Entry function
async function main() {
await init();
await server.start();
};
main();
然后,就可以使用 Global.db
来调用 MongoDB 了,例如:
export async function ApiGetPost(call: ApiCall<ReqGetPost, ResGetPost>) {
let op = await Global.db.collection<Post>('Post').findOne({
_id: call.req._id
});
// ...
}
通常你不需要手动去关闭连接,保持数据库的长连接可以让你的接口响应更加迅速。
表结构映射
在上面的例子中看到,我们可以通过 db.collection<类型名>('表名')
这样的写法来告诉 TypeScript 表结构类型。
但是,你又给自己埋下了一个小坑。
墨菲定律:可能犯错的一定会犯错。
如果表名拼写错了呢?如果类型名关联错了呢?这些都是常有的事。
同样,你也可以利用 TypeScript 的类型系统,在一开始就规避这些问题。
首先,定义一个 interface
,显示指明所有表名及其类型:
export interface DbCollectionType {
// 表名:类型名
Post: DbPost,
User: DbUser,
Comment: DbComment
}
然后,自行实现一个 .collection
方法,利用 TS 的泛型,自动关联表名和类型名:
import { Collection, Db, MongoClient, OptionalId } from "mongodb";
export class Global {
static db: Db;
static async initDb() { ... }
static collection<T extends keyof DbCollectionType>(col: T): Collection<OptionalId<DbCollectionType[T]>> {
return this.db.collection(col);
}
}
现在,你就可以使用 Global.collection
来替代 Global.db.collection
了,享有了自动代码提示和类型约束。
ObjectId 和 Date
ObjectId
在 TSRPC 下,你可以在协议中直接使用 ObjectId
类型,框架会自动完成传输前后的类型转换。
ObjectId
是 MongoDB 默认的 _id
类型,因为其引用自 mongodb
NPM 包(前端未安装),所以通常无法在前端通用。
但通过 脚手架工具 创建的 TSRPC 全栈项目却做到了这一点,其原理是在前端项目中的 env.d.ts
中定义了如下类型:
declare module 'mongodb' {
export type ObjectId = string;
export type ObjectID = string;
}
declare module 'bson' {
export type ObjectId = string;
export type ObjectID = string;
}
因此即便协议定义中包含了 import { ObjectId } from 'mongodb'
,亦可以在前端通用。ObjectId
在前端被解析为普通字符串,但在后端将自动转换为 ObjectId
类型。
Date
在 TSRPC 下,你可以在协议中直接使用 Date
类型。
推荐直接使用 Date
类型而不是时间戳,通常它在数据库管理工具中可读性更佳,看数据、维护都更轻松。