MongoDB
为什么前端都要用这个DBMS。
太长不看
- 用到mongoose库,是一个Object Data Modeling (ODM)
- Model接受schema定义好的 表的形状,生成model实例,用于crud
- 关于dotenv的使用,es6和commonjs是有一些区别,详见其官网介绍
- 错误处理就是对可能发生(预期到)的错误用next传递给对应的错误处理器
介绍MongoDB的使用
PS:我要使用docker
使用.env 把一些不能提交到repo的信息给注入到项目运行时里
- 要把这个.env加入到.gitignore
- docker-compose 也会用到.env,一般来说,docker-compose的env 用于部署,所以不会和项目的.env共处,这里放到一块原因就是因为不会部署。
用mongoose 连接 MongoDB
在开启docker中的 MongoDB数据库容器之后,就可以在node中连接这个数据库,mongoose提供了.connect方法可以很方便的传入数据库Url 以及必要的验证信息,这个方法返回一个promise,如果发生任何连接问题,都可以清楚的在catch中处理和report。
// https://www.typescriptlang.org/docs/handbook/module-resolution.html#module-resolution-strategies
// 这里把moduleResolution 明确地改为了 node (照理说默认应该就是node),至于这么改的到底 查看上述网址
import mongoose from "mongoose";
const { MONGO_URL, MONGO_USER, MONGO_PASS } = process.env;
mongoose
.connect(MONGO_URL, {
user: MONGO_USER,
pass: MONGO_PASS,
})
.then(() => {
// MongoDB Database
console.log("成功连接木地板数据库");
})
.catch((err) => {
console.log(" connecting to the 木地板 database发生错误");
console.log(err);
process.exit();
});
在这里就已经使用了.env注入到process.env里的配置信息,这是最佳实践之一。
ps:连接配置的格式
验证env参数的有效性
这同样也是最佳实践之一,env参数很有可能被错误的设置(typo最常见,因为没有智能提示),所以必须使用有效的工具来确保env是被正确地注入到运行时。使用 envalid库
介绍Model
MVC定义说的很严谨,model用于管理和处理数据,降到实际层面,model一般 至少抽象出了常用的CRUD方法,mongoose 是这里要使用到的类库,
它使用schema来定义表(这里叫collection)的形状,然后用定义后的形状生成model,最后用生成的model暴露的方法进行常规CRUD。
src/controller/post/post.model.ts
import mongoose, { Schema } from "mongoose";
import IPost from "./post.interface";
// https://mongoosejs.com/docs/typescript.html
// 1. Imports an interface representing a document in MongoDB.
// 2. Create a Schema corresponding to the document interface.
const postSchema = new Schema<IPost>({
content:{type:String,required:true},
author:{type:String,required:true},
title:{type:String,required:true},
});
const postModel = mongoose.model<IPost & mongoose.Document>("Post", postSchema);
export default postModel;
这里暴露出的postModel上 就自带对数据库操作的抽象,可以直接在controller中使用model来操作数据库。
(而不是在controller来编写对数据库的操作逻辑)
还有一点需要注意,这个接口对Schema的约束属于,只关心存在的成员名称是否正确,而既不关心是否存在某个成员以及其required是否与接口中的类型相符
下面是使用model后的controller
import express, { Router } from "express";
import Controller from "../../interface/controller.interface";
import postModel from "./post.model";
export default class PostController implements Controller {
router: Router;
/**
* 不应该对外暴露,防止被改变,只暴露一个getter
*/
private childRoutePath: string = "/aChildRoute";
constructor() {
this.router = Router();
this.useRoutes();
}
private useRoutes = () => {
this.router.post(this.childRoutePath, this.createPost);
this.router.get(this.childRoutePath, this.findPosts);
this.router.get(`${this.childRoutePath}/:id`, this.findById);
this.router.put(`${this.childRoutePath}/:id`, this.modifyPost);
this.router.delete(`${this.childRoutePath}/:id`, this.deletePost);
};
private createPost = async (req: express.Request, res: express.Response) => {
const post = req.body;
await postModel.create(post);
res.send(req.body);
};
private findPosts = async (req: express.Request, res: express.Response) => {
const posts = await postModel.find();
res.send(posts);
};
private findById = async (req: express.Request, res: express.Response) => {
const id = req.params.id;
const post = await postModel.findById(id);
res.send(post);
};
private modifyPost = async (
req: express.Request,
res: express.Response,
next: express.NextFunction
) => {
const id = req.params.id;
const newPost = req.body;
postModel
.findByIdAndUpdate(id, newPost, { new: true })
.then((post) => {
res.send(post);
})
.catch((err) => {
next(err);
});
};
private deletePost = async (
req: express.Request,
res: express.Response,
next: express.NextFunction
) => {
const id = req.params.id;
postModel
.findByIdAndDelete(id)
.then((successResponse) => {
if (successResponse) {
res.send(200);
} else {
res.send(404);
}
})
.catch((err) => {
next(err);
});
};
/**
* 有可能会丢失 this
*/
get path() {
return this.childRoutePath;
}
}