我如何从 Express.js 迁移到 Next.js API 路由
这是我上一篇文章Explore Next.js 9 API Routes的后续。 经过深思熟虑,我决定放弃Express.js并转入 API Routes。在这一点上,我的项目仍然很简单——我没有太多代码。我认为最好在项目变得复杂之前立即采取行动。 迁移到 Next.js API 路由 为了使用新的 API 路由,我需要通过运行将我的 Next.js 模块更新到 v9: npm i next@la
这是我上一篇文章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;
你可以看到我是如何使用req
和res
的。让我们看看 Next.js 9 API Routes'way:
export default function handle(req, res) {
res.end('Hello World');
}
句柄函数具有相同的语法:它采用相同的req
和res
。更好的是,Next.js 9 的 API Routes 实现了类似的 Express.js 的Middlewares
,包括解析器req.body
和辅助函数res.status
和res.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
的路由。
我们来看看我画的这张图:
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 Import 或 ES6 Export。但是,有几个函数仍然使用module.exports
。这导致了我在下面提到的一个问题。 (见FIXME: ... is not a function
)。
因此,要保持一致。我建议对整个代码库使用 ES6 导入/导出。
其他问题
// FIXME:没有错误的空白页
注意:我在下面遇到的这个特殊问题并非源自 Next.js。你可以跳过它!
我遇到的一个问题是,当我运行next dev
时,终端显示build page: /
、compiling...
和compiled successfully
。但是,当我访问http://localhost/
时,我看到一个空白页面,标签的状态栏显示加载指示。
当我查看“网络”选项卡时,我看到GET localhost:3000/
继续运行而没有任何响应。 (无状态、响应标头、有效负载)。
这个问题最烦人的是没有 500 internal server error 或控制台中的任何红色错误文本。
我查看了我的代码并检查了所有语法。一切看起来都很好。我的意思是我刚刚将我的代码的工作版本复制粘贴到新格式。如果我的代码中有错误,它应该发生在我进行迁移之前。
幸运的是,当我尝试运行next build
时,我看到了错误:
我是在做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 build
和next 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 dev
和next start
。
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
}
结论
我设法将我的代码库更改为使用 Next.js API 路由。它开启了 serverless 的可能性,我将很快对此进行探索。
我仍然不时遇到这个新的 Next.js API 路由的问题。当我这样做时,我会确保将其包含在本文中。祝你部署 Next.js API 路由好运。
更多推荐
所有评论(0)