Node+Express+MongoDB 服务端开发小结

在刚过去的一次Hackathon里面,写了一个服务端的应用。不过由于时间关系,并没有写完 Orz。不过呢,也趁着这个机会学到了一些新的东西。下面就来具体介绍一下吧~

服务端技术栈的选择

服务端之所以选择Node,显而易见,是因为JavaScript圈的生态。之前也有用过Python+Flask/Django写过后端,有些地方就不如Node,比如异步、回调、箭头函数等。Express相当于Python里面的Flask,能够简化一些服务端的写法,比如初始化服务、路由等。之前在用Node做项目的时候,数据库我一直避开了,因为考虑到自己的项目对存取没有太大要求,只需要方便操作就行了。因此我用了lowdb,一个基于json的本地数据库。你的数据库不大的话,那么用这样一个方式就可以了,而且读取json非常的便捷。此外还用过SQLite,这是本地的SQL数据库,查询需要写SQL。这次的项目里尝试了一下MongoDB,一个非关系型数据库,基于对象和文档进行存储。

初始化Node及Express

首先我们$npm init一个空项目出来,添加express库$yarn add express,新建一个server.js,像下面一样添加几行代码,就能实现一个最基本的服务。访问localhost:3000/就能看到Hello World的输出。

1
2
3
4
5
6
const express = require('express')
const app = express()
const port = 3000

app.get('/', (req, res) => res.send('Hello World!'))
app.listen(port, () => console.log(`Listening on port ${port}!`))

初始化数据库

首先我们需要在服务器或本地安装MongoDB。我是在Windows平台上安装的,安装包体积大约有500M,网上有很多教程,中间有一个步骤会问你是否需要安装MongoDB Compass,建议可以安装一下,能够可视化的调试数据库。Windows上的话需要手动去启动MongoDB的服务,服务默认的端口是27017。在Node项目中,我们需要使用相关的库来对数据库进行操作,这里我用了Mongoose。可以很优雅的连接MongoDB的数据库。我创建了一个db.js模块,将数据库作为一个module。可以看到我连接的是MongoDB里面的test数据库。

1
2
3
4
5
6
7
8
9
10
11
12
13
var mongoose = require("mongoose");
let DB_URL = "mongodb://localhost:27017/test";

mongoose.connect(
DB_URL,
{ useNewUrlParser: true }
);

mongoose.connection.on("connected", function() {
console.log("Mongoose connection open to " + DB_URL);
});

module.exports = mongoose;

项目结构

组件化是很有必要的,特别是当项目逐渐变得复杂的时候。于是我用了这样一个结构,将各个功能之间的耦合程度降低。routes文件夹存放不同任务的路由,models文件夹存放MongoDB中对象的定义,controllers里面是对于不同类型数据的增删改查的操作。因此整个目录就像下面这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
├── controllers
│   ├── taskController.js
│   └── userController.js
├── db.js
├── models
│   ├── task.js
│   └── user.js
├── package.json
├── routes
│   ├── task.js
│   └── user.js
├── server.js
└── yarn.lock

数据模型的构建

因为使用了MongoDB,根据Mongoose提供的对象化的操作接口,需要将数据模型抽象出来。以post.js为例,我们将post抽象出来,创建一个schema,告诉数据库一个post都有哪几个部分,每个部分对应的类型。这样的好处是在数据库设计时就能考虑到不同数据之间的关系,并将它们嵌起来,比如post中嵌入评论段。MongoDB的好处就是像对象一样管理数据,减少查询的次数。但值得注意的是,如果关系更复杂的话(之后我就遇到了这个问题),嵌套数据反而很麻烦。否则就得像传统SQL设计数据表一样,但会造成很多的信息冗余。这个问题可能是后期还需要进一步探索的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var mongoose = require("mongoose");
var Schema = mongoose.Schema;

let PostSchema = new Schema({
id: String,
title: String,
type: String,
author: String,
sendTime: Date,
tags: [String],
content: String
});

module.exports = {
schema: PostSchema,
model: mongoose.model("Post", PostSchema)
};

数据控制器的实现

对于不同的请求,我们需要根据请求的内容来对数据库进行存取,并返回对应的值。首先我们将数据库模型实例化,再根据后面路由调用的函数进行相应的操作。具体的查找筛选可以参考mongoose的文档。总之数据模型的层级不能太深,否则查找起来非常的难受,就会怀念SQL。

1
2
3
4
5
6
7
8
9
10
var GroupInstance = require("../models/group").model;
var TaskInstance = require("../models/task").model;

// Task index
exports.index = (req, res) => res.send("This is task index");

// List all users
exports.list_all = (req, res) => {
GroupInstance.find({}, (err, items) => res.json({ tasks: items }));
};

路由的实现

这个还是相对比较简单的。需要用到不容的控制器,并定义不同路由所触发的对应控制器的函数。再将其导出成模块供我们主服务使用。像 task.js

1
2
3
4
5
6
7
8
9
10
var express = require("express");
var router = express.Router();

var taskController = require("../controllers/taskController");

router.get("/", taskController.index);
router.get("/list-all", taskController.list_all);
router.post("/new", taskController.new);

module.exports = router;

模块的整合

到最后,我们就能将所有的模块整合在一起,此时我们的主文件 server.js 就应该如下。其中我们还使用了 body-parser 模块,来解析post请求时发送的数据。引入不同的router,并定义他们的前缀,这样就可以通过这样的方式进行请求:localhost:3000/task/new

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const express = require("express");
const bodyParser = require("body-parser");
const app = express();
const port = 3000;

// Parser middleware
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));

// Declare routers
var indexRouter = require("./routes/index");
var userRouter = require("./routes/user");
var taskRouter = require("./routes/task");

// Use different routers
app.use("/", indexRouter);
app.use("/user", userRouter);
app.use("/task", taskRouter);

app.listen(port, () => console.log(`Listening on port ${port}!`));

调试热重载

我们不可能每次都手动去启动我们的服务,我们可以使用 nodemon 模块来实现热重载,这样每次修改文件都能自动的启动服务。我们可以在 package.json 里面这样写:

1
2
3
"scripts": {
"dev": "nodemon server.js"
}

这样我们就可以用命令 yarn dev 来启动一个开发版本的服务了。

总结

以上就是我在短短24小时不到的时间里面学到的一些Node做后端的方法。当然,也遇到了很多的问题,特别是在数据库上。我认为在数据库的构建上需要多花点时间去考虑,MongoDB有优势,也有一定的局限。Node依靠Javascript的生态圈使其在网页开发上有了无可比拟的优势,在需要快速构建应用的场景,Node不失为一个很好的选择。

打赏支持一下~