只需cv,实战中学习next | 集成后端服务

443 阅读5分钟

王志远,微医前端技术部

前言

要模拟真实项目中 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 请求在浏览器可以正常请求,但如果在项目中,就会出现如下问题,这就是我们常说的跨域,在这不过多介绍,一句话:非同源的数据请求,浏览器会对服务器的响应进行拦截并报错。

image-20220501201055266

要想实现 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直接返回

2022-05-02 18.06.06

实现连接 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;

查看效果

2022-05-02 21.28.06

至此,我们也就完成了【后端服务的实现】啦!