这是我上一篇文章Explore Next.js 9 API Routes的后续。

经过深思熟虑,我决定放弃Express.js并转入 API Routes。在这一点上,我的项目仍然很简单——我没有太多代码。我认为最好在项目变得复杂之前立即采取行动。

迁移到 Next.js API 路由

为了使用新的 API 路由,我需要通过运行将我的 Next.js 模块更新到 v9:

npm i next@latest react@latest react-dom@latest. 这会将 Next.js 和 React.js 更新到最新版本。

虽然这是一个重大更新,但我没有发现任何特别影响我的重大更改。但是,如果有任何适合您的,有这个升级指南可以帮助您解决任何问题。

重写代码库 - 更像是大量的复制粘贴

Express.js 到下 9 个 API 路由

在我当前的 express.js 服务器中,要访问/api/authenticate处的端点,我在/server/components/account/accountController.js中的代码是:

// accountController.js
const express = require('express');

const User = require('../../api/models/userModel');

// In server.js, I called app.use('/api', AccountController);
const AccountController = express.Router();

AccountController.post("/authenticate", (req, res) => {
  const { email, password } = req.body;
  User.findByCredentials(email, password)
    .then(user => user.generateSessionId())
    .then(sessionId => {
      const { name } = user;
      res
        .cookie("sessionId", sessionId, { httpOnly: true, secure: true })
        .send(`welcome my homie, ${name}`);
    })
    .catch(e => {
      // reject due to wrong email or password
      res.status(401).send("who are u, i dun know u, go away");
    });
});
module.exports = AccountController;

你可以看到我是如何使用reqres的。让我们看看 Next.js 9 API Routes'way:

export default function handle(req, res) {
  res.end('Hello World');
}

句柄函数具有相同的语法:它采用相同的reqres。更好的是,Next.js 9 的 API Routes 实现了类似的 Express.js 的Middlewares,包括解析器req.body和辅助函数res.statusres.send。这意味着我不必做很多改变。

// FIXME:Next.js API 路由中没有 res.cookie

Next.js 9 API Routes 中似乎没有res.cookie辅助函数。我需要重写函数,回退到http.ServerResponse的setHeader(因为NextApiResponse扩展了http.ServerResponse):

res.cookie("sessionId", sessionId, { httpOnly: true, secure: true })变成

res.setHeader('Set-Cookie',`sessionId=${sessionId}; HttpOnly; Secure`)

.

我在zeit/next.js上创建了一个添加res.cookie的功能请求。我希望他们会添加它。现在,我必须坚持使用res.setHeader

// TODO:创建 API 路由版本/api/authenticate

我创建了pages/api/authenticate.js

// authenticate.js
export default (req, res) => {
  const { email, password } = req.body;
  User.findByCredentials(email, password)
    .then(user => user.generateSessionId())
    .then(sessionId => {
      const { name } = user;
      res
        .setHeader("Set-Cookie", `sessionId=${sessionId}; HttpOnly; Secure`)
        .send(`welcome my homie, ${name}`);
    })
    .catch(e => {
      // reject due to wrong email or password
      res.status(401).send("who are u, i dun know u, go away");
    });
};

完美,这就是我将代码从 Express.js 转换为 Next.js API Routes 的方式:只需复制粘贴并进行一些小改动。通过这样做,我放弃了 Express Router,使代码更加简洁。我对每个 API 端点都做了同样的事情。

呃,哦。我们的数据库在哪里?

回到 Express.js 版本,我的npm start运行这个server.js脚本:

const express = require("express");
const mongoose = require("mongoose");
const AccountController = require("./components/account/accountController");
const app = express();
mongoose.connect(process.env.MONGODB_URI, {
  useNewUrlParser: true,
  useFindAndModify: false,
  useCreateIndex: true
});
app.use("/api", AccountController);
app.listen(process.env.PORT);

(为了简单起见,我删除了 Next.js 自定义服务器集成)

请注意,mongoose.connect()是我连接到数据库的方式。然后由app.use("/api", AccountController);处理到/api/authenticate的路由。

我们来看看我画的这张图:

Express.js 和 Next.js 没有 MongoDB 连接

Next.js 版本中没有 MongoDB 连接

如您所见,在 Express.js 版本中,服务器保持运行并保持连接。但是,在 Next.js 版本中,服务器没有初始化连接的起点。

每个端点上添加mongoose.connect()怎么样(在/pages/api下的每个单个.js。嗯,情况并非如此。

想象一下,每次命中一个 API Route 都会调用mongoose.connect(),因此会调用多个mongoose.connect()。但是,您只能拨打mongoose.connect()一次。否则,您将收到此错误:

MongooseError: You can not 'mongoose.connect()' multiple times while connected

// TODO:只维护一个Mongoose连接

一定有办法检查是否有猫鼬连接。如果没有,我们只会尝试连接。

这是我的方法:

// db.js
import mongoose from 'mongoose';

export default async () => {
  if (mongoose.connections[0].readyState) return;
  // Using new database connection
  await mongoose.connect(process.env.MONGODB_URI, {
    useNewUrlParser: true,
    useFindAndModify: false,
    useCreateIndex: true,
  });
};

_Edit:更新正确的方法

成功连接 MongoDB 后,mongoose.connections[0].readyState将是 1 (true)。下次调用该函数时,它将简单地返回。

剩下要做的就是在每个 API 端点中从 db.js 导入函数。

// authenticate.js
import connectToDb from '../../../api/db';

export default async (req, res) => {

  await connectToDb();

  const { email, password } = req.body;
  User.findByCredentials(email, password)
    .then(user => user.generateSessionId())
    .then(sessionId => {
      const { name } = user;
      res
        .setHeader("Set-Cookie", `sessionId=${sessionId}; HttpOnly; Secure`)
        .send(`welcome my homie, ${name}`);
    })
    .catch(e => {
      // reject due to wrong email or password
      res.status(401).send("who are u, i dun know u, go away");
    });
};

我将处理程序设置为async函数,以便我可以在connectToDb()上使用关键字 await。通过使用 await 关键字,我们确保connectToDB()在其他任何事情之前完成。

而已!

替代方式:使用中间件

可以通过包装处理函数来实现“中间件”。

创建一个dbMiddleware.js:

import mongoose from 'mongoose';

const connectDb = handler => async (req, res) => {
  if (mongoose.connections[0].readyState) return handler(req, res);
  // Using new database connection
  await mongoose.connect(process.env.MONGODB_URI, {
    useNewUrlParser: true,
    useFindAndModify: false,
    useCreateIndex: true,
  })
  return handler(req, res);
}

export default connectDb;

之后在我的 API 函数中,我包装了处理函数。

import connectDb from '../../../api/middlewares/dbMiddleware.js';
const handler = (req, res) => { 
  const { email, password } = req.body;
  User.findByCredentials(email, password)
    .then(user => user.generateSessionId())
    .then(sessionId => {
      const { name } = user;
      res
        .setHeader("Set-Cookie", `sessionId=${sessionId}; HttpOnly; Secure`)
        .send(`welcome my homie, ${name}`);
    })
    .catch(e => {
      // reject due to wrong email or password
      res.status(401).send("who are u, i dun know u, go away");
    });
};
export default connectDb(handler);

在这篇文章中了解更多信息。

TODO:导入导出时保持一致

在使用Express.js的时候,我没有实现Babel,无法使用ES6 Import / Export

当我开始使用 API Routes(包括 Babel)时,我将部分代码库更改为使用 ES6 ImportES6 Export。但是,有几个函数仍然使用module.exports。这导致了我在下面提到的一个问题。 (见FIXME: ... is not a function)。

因此,要保持一致。我建议对整个代码库使用 ES6 导入/导出

其他问题

// FIXME:没有错误的空白页

注意:我在下面遇到的这个特殊问题并非源自 Next.js。你可以跳过它!

我遇到的一个问题是,当我运行next dev时,终端显示build page: /compiling...compiled successfully。但是,当我访问http://localhost/时,我看到一个空白页面,标签的状态栏显示加载指示。

I migrate to NextJS API Routes - Page keeps loading while showing no error

当我查看“网络”选项卡时,我看到GET localhost:3000/继续运行而没有任何响应。 (无状态、响应标头、有效负载)。

这个问题最烦人的是没有 500 internal server error 或控制台中的任何红色错误文本。

我查看了我的代码并检查了所有语法。一切看起来都很好。我的意思是我刚刚将我的代码的工作版本复制粘贴到新格式。如果我的代码中有错误,它应该发生在我进行迁移之前。

幸运的是,当我尝试运行next build时,我看到了错误:

I migrate to NextJS API Routes - Build error ENOENT node-sass vendor

我是在做next build时才发现的

node-sass 在做什么?这完全无关紧要。然后,我想到了那个愚蠢的 IT 笑话“你试过把它关掉再打开吗?”。好吧,不,我并没有真正重新启动我的电脑。我所做的是运行npm rebuild。这允许我“重置/重新启动”节点模块(当然包括node-sass)。它只是神奇地起作用。删除我的 node_modules 文件夹并运行npm install将达到同样的效果。

如有疑问,请运行npm rebuild

运行next build现在显示compiled successfully和运行next dev工作:没有更多的空白页......好吧,但现在我们有一些 500 内部服务器错误

// FIXME: ... 不是函数

如果运行生产版本,可能会遇到UnhandledPromiseRejectionWarning: TypeError: ... is not a function

经过一些试验和错误,我注意到如果我使用ES6 import而不是require,错误就会消失。

我猜是由于某种原因,webpack 没有正确解析require。我注意到在我的代码中我使用了两种不同的变体:我按require导入函数,但按export default导出函数。这可能是问题的原因。

因此,继续从require / modules.export更改为import / export。如果您不指定export *default*,则必须明确提及函数的名称。例如:

import { nameOfFunction } from 'path/to/theFunction'

// FIXME:编译后无法覆盖模型

我认为这实际上不是你的错误。你可能会认为是因为你多次导入了model.js文件。当我使用 Express.js 时,我必须做同样的事情,但没有遇到这个问题。我怀疑这是由于热模块更换 (HMS)。因为 HMS 是在运行中编译的,所以 model.js 有可能被多次编译,从而导致问题。

我通过尝试使用next buildnext start为生产构建服务来测试我的理论。没有错误,因为当时 Webpack 没有进行编译。

这是该问题的解决方法:

export default mongoose.models.User || mongoose.model('User', UserSchema);

如您所见,我们首先查看 mongoose.models.User 是否存在,如果不存在则仅对其建模。

你好 Next.js API 路由,再见 Express.js

卸载冗余依赖

由于我们不再使用 Express.js,因此删除它总是一个好主意。

使用 Express.js,我还需要卸载两个依赖项:nodemon和cookie-parser。我

当我更改代码时,我曾经需要nodemon来重新启动我的服务器。这不再需要,因为从现在开始我将使用 Webpack 的热模块替换。

我以前需要cookie-parser才能访问req.cookies。现在不再需要了,因为 Next.js 9 已经提供了这样做的方法。

我继续通过运行卸载它们:

npm uninstall express nodemon cookie-parser

确保从代码中删除任何import / require个提到的依赖项。

更改 package.json 中的脚本

在我的 Express.js 版本中,我的脚本是:

"scripts": {
    "dev": "nodemon server/server.js",
    "build": "next build",
    "start": "cross-env NODE_ENV=production node server/server.js",
 }

对于npm run dev,我 nodemon 我的自定义服务器server.js。对于npm run start,我 node 我的server.js

转向 API 路由,我不再需要自定义服务器或热重载。我所要做的就是运行next devnext start

"scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
}

结论

我设法将我的代码库更改为使用 Next.js API 路由。它开启了 serverless 的可能性,我将很快对此进行探索。

我仍然不时遇到这个新的 Next.js API 路由的问题。当我这样做时,我会确保将其包含在本文中。祝你部署 Next.js API 路由好运。

Logo

MongoDB社区为您提供最前沿的新闻资讯和知识内容

更多推荐