王志远,微医前端技术部
前言
要模拟真实项目中 SSR 的应用,真正的后端服务和数据库无可避免,我们就基于 express+mongodb 启动一个后端服务吧!
本文分享两种项目实现方案:express-generator 脚手架 、 原生 express 集成;
准备启动脚本
在 package.json 的script中新增如下脚本
"server": "nodemon server"
nodemon 是一个 node 项目启动器,具有失败重启功能,如果没有安装nodemon,可以执行如下命令进行安装
npm i nodemon -g
后面的启动项目都是指运行如下命令
npm run server
搭建项目框架
express-generator 脚手架(不推荐,只是记录下)
安装
npm i express-generator -g
创建项目
express-generator api
原生 express 集成(推荐)
既然是学习项目,还是多熟悉下吧,我们手写一个 koa 服务器并连接数据库实现增删改查逻辑。实现目录如下
- 实现可访问的 express 服务
- 实现跨域处理
- 实现 post 请求的请求体解析
- 实现连接 mongodb 数据库
- 实现增删改查接口
原生 express 集成实现
实现可访问的 express 服务
安装依赖
yarn add express@4.17.3
实现内容
新建server/index.js,实现如下内容
let express = require("express");
let app = express();
app.get("/hello", async (req, res) => {
res.send("hello Express");
});
app.listen(4000, () => {
console.log("服务器在 4000 端口启动!");
});
查看效果
实现跨域处理
上面的 get 请求在浏览器可以正常请求,但如果在项目中,就会出现如下问题,这就是我们常说的跨域,在这不过多介绍,一句话:非同源的数据请求,浏览器会对服务器的响应进行拦截并报错。
要想实现 post 请求模拟,我们先处理下跨域问题
安装依赖
yarn add cors@2.8.5
实现内容
新建server/index.js,实现如下内容
let express = require("express");
let app = express();
let cors = require("cors");
app.use(
cors({
origin: ['http://localhost:3000'],
credentials: true,
allowedHeaders: "Content-Type,Authorization",
methods: "GET,HEAD,PUT,PATCH,POST,DELETE,OPTIONS"
})
);
app.get("/hello", async (req, res) => {
res.send("hello Express");
});
app.listen(4000, () => {
console.log("服务器在 4000 端口启动!");
});
这时就能正常请求了
实现 post 请求的请求体解析
body-parser可以将 post 请求参数处理成对象并挂载在req.body上
安装依赖
yarn add body-parser@1.19.2
接入规则
let bodyParser = require("body-parser");
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
实现内容
server/index.js改为如下内容
let express = require("express");
let bodyParser = require("body-parser");
let app = express();
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
app.get("/hello", async (req, res) => {
res.send("hello Express");
});
app.post("/api/register", async (req, res) => {
let user = req.body;
res.send({ code: 0, data: user });
});
app.listen(4000, () => {
console.log("服务器在 4000 端口启动!");
});
查看效果
前端提交表单,后端将req.body直接返回
实现连接 mongodb 数据库
要连接,首先你得有 mongo 数据库,推荐文章安装 mongo 数据库;下文默认已经存在 mongodb 数据库了,并且已经创建了next-sty名称的数据库,我们来进行项目中的连接操作。
安装依赖
yarn add connect-mongo@4.6.0 express-session@1.17.2
接入规则
先将数据库的地址信息和用于会话加盐的密钥放在配置文件中,创建server/config.js文件,写入如下内容
module.exports = {
secret: "wzyan",
dbUrl: "mongodb://127.0.0.1/next-db",
};
然后在项目中进行接入
let session = require("express-session");
var MongoStore = require("connect-mongo");
app.use(
session({
secret: config.secret,
resave: false,
saveUninitialized: true,
store: MongoStore.create({
mongoUrl: config.dbUrl,
mongoOptions: {
useNewUrlParser: true,
useUnifiedTopology: true,
},
}),
})
);
实现内容
server/index.js内容修改为如下
let express = require("express");
let bodyParser = require("body-parser");
let cors = require("cors");
let session = require("express-session");
let config = require("./config");
var MongoStore = require("connect-mongo");
let app = express();
app.use(
cors({
origin: ["http://localhost:3000"],
credentials: true,
allowedHeaders: "Content-Type,Authorization",
methods: "GET,HEAD,PUT,PATCH,POST,DELETE,OPTIONS",
})
);
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
app.use(
session({
secret: config.secret,
resave: false,
saveUninitialized: true,
store: MongoStore.create({
mongoUrl: config.dbUrl,
mongoOptions: {
useNewUrlParser: true,
useUnifiedTopology: true,
},
}),
})
);
app.listen(4000, () => {
console.log("服务器在 4000 端口启动!");
});
查看效果
这个效果我们在实现了增删改查接口后验证
实现用户增删改查接口
有了数据库,又完成了连接,我们就可以开始实现业务接口啦!很简单的增删改查接口
安装依赖
yarn add mongoose@6.3.0
接入规则
mogonse 操作数据库的方式是通过 Model 的概念实现的,我们来实现下用户的接口,自然需要创建用户的模型。我们创建server/db.js文件,实现如下内容,导出用户模型
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
const ObjectId = Schema.Types.ObjectId;
let config = require("./config");
const conn = mongoose.createConnection(config.dbUrl, {
useNewUrlParser: true,
useUnifiedTopology: true,
});
const UserModel = conn.model(
"User",
new Schema(
{
username: { type: String },
password: { type: String },
},
{ timestamps: { createdAt: "created", updatedAt: "updated" } }
)
);
module.exports = {
UserModel,
};
然后在服务端引入,实现接口即可
server/index.js
let express = require("express");
let bodyParser = require("body-parser");
let cors = require("cors");
let Models = require("./db");
let session = require("express-session");
let config = require("./config");
var MongoStore = require("connect-mongo");
let app = express();
app.use(
cors({
origin: ["http://localhost:3000"],
credentials: true,
allowedHeaders: "Content-Type,Authorization",
methods: "GET,HEAD,PUT,PATCH,POST,DELETE,OPTIONS",
})
);
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
app.use(
session({
secret: config.secret,
resave: false,
saveUninitialized: true,
store: MongoStore.create({
mongoUrl: config.dbUrl,
mongoOptions: {
useNewUrlParser: true,
useUnifiedTopology: true,
},
}),
})
);
app.get("/api/users", async (req, res) => {
let users = await Models.UserModel.find();
res.send({ code: 0, data: users });
});
app.get("/api/users/:id", async (req, res) => {
let user = await Models.UserModel.findById(req.params.id);
res.send({ code: 0, data: user });
});
app.post("/api/register", async (req, res) => {
let user = req.body;
user = await Models.UserModel.create(user);
res.send({ code: 0, data: user });
});
app.listen(4000, () => {
console.log("服务器在 4000 端口启动!");
});
在前台项目中接入接口
add.tsx中修改为如下内容
import UserLayout from "./index";
import router from "next/router";
import { Form, Input, Button, Icon, message } from "antd";
import axios from "../../utils/axios";
function UserAdd(props) {
const { getFieldDecorator } = props.form;
async function handleSubmit(event) {
event.preventDefault();
let values = props.form.getFieldsValue();
let response = await axios.post("/api/register", values);
if (response.data.code === 0) {
console.log(response);
router.push("/user/list");
} else {
message.error("添加用户失败");
}
}
return (
<UserLayout>
<Form
onSubmit={handleSubmit}
className="login-form"
style={{ maxWidth: "300px" }}
>
<Form.Item>
{getFieldDecorator("username", {
rules: [{ required: true, message: "Please input your username!" }],
})(
<Input
prefix={<Icon type="user" style={{ color: "rgba(0,0,0,.25)" }} />}
placeholder="Username"
/>
)}
</Form.Item>
<Form.Item>
{getFieldDecorator("password", {
rules: [{ required: true, message: "Please input your Password!" }],
})(
<Input
prefix={<Icon type="lock" style={{ color: "rgba(0,0,0,.25)" }} />}
type="password"
placeholder="Password"
/>
)}
</Form.Item>
<Form.Item>
<Button
type="primary"
htmlType="submit"
className="login-form-button"
>
添加用户
</Button>
</Form.Item>
</Form>
</UserLayout>
);
}
export default Form.create({ name: "UserAdd" })(UserAdd);
list.tsx中修改为如下内容
import UserLayout from "./index";
import { List } from "antd";
import Link from "next/link";
import axios from "../../utils/axios";
function UseList(...params) {
let props = params[0];
console.log("4.UseList.render", params);
return (
<UserLayout>
<List
header={<div>用户列表</div>}
footer={<div>共计多少{props?.list?.length}个用户</div>}
bordered
dataSource={props.list}
renderItem={(item: any) => (
<List.Item key={item._id}>
<Link
as={`/user/detail/${item._id}`}
href={{ pathname: `/user/detail`, query: { id: item._id } }}
>
<a>{item.username}</a>
</Link>
</List.Item>
)}
/>
</UserLayout>
);
}
// 获取此组件的初始化对象 此函数的返回值将成为此组件的属性对象
UseList.getInitialProps = async (ctx) => {
console.log("2.UseList.getInitialProps ctx", ctx);
// let list = [
// { _id: 1, username: "zhangsan", password: "1" },
// { _id: 2, username: "lisi", password: "2" },
// ];
let response = await axios({ url: "/api/users", method: "GET" });
return { list: response.data.data };
// return { list };
};
export default UseList;
查看效果
至此,我们也就完成了【后端服务的实现】啦!