使用Zeit Now设计无服务器Express.js API

无服务器lambda非常适合单个REST API端点。 它们易于设置,与设置多个服务器实例和负载平衡器相比,没有麻烦,而且在许多情况下它们更便宜。 在线上有很多教程,将引导您逐步进行设置。 但是,使用lambda处理多个终结点(例如较大的API中的整个路由)怎么办?

我们的目标:我们将构建具有多个路由的Express.js API,每个路由都由一个lambda处理。 Lambda将使用Zeit Now进行部署,并且仅包含响应特定路由中的请求所需的代码。 我们还将创建一个开发服务器,因为对本地运行lambda进行调试的支持很差。

我们为什么首先要这样做?

  • 代码共享:给定路由中的所有端点都有可能使用相同的数据模型或至少某些共享代码来支持它们。 将整个路由放在相同的lambda中将减少样板代码(例如数据库连接)的数量,而不必加载整个API来响应单个请求。
  • Express的所有功能: Express具有许多出色的功能,可以使您的代码简洁易懂。 与传统的Now现在默认使用的Node HTTP Server API相比,它们的API更易于使用。 另外,您还获得了对中间件的支持,这将进一步减少重复的代码。
  • 冷启动次数少:当lambda一段时间没有被调用时,就会发生“冷启动”,从而导致其花更长的时间来响应请求。 当使用同一个lambda来响应多个端点时,从统计上讲,它将更频繁地被调用。 由于Zeit Now最多可以缓存lambda 15分钟,因此更有可能在从缓存中卸载之前再次调用该lambda。
  • 无服务器范式: Zeit建议我们使用@now/node而不是@now/node-server因为它坚持无服务器lambda实际上只是功能的想法。 我们可以使用@now/node因为我们只是公开了一个要运行的函数,而不是自己创建服务器实例。 这样,我们将坚持传统的无服务器范例。
  • 较小的内存占用空间:一种流行的解决方案是将整个API放在同一lambda中。 尽管这是可行的并且可以实现,但是它会显着增加所需的内存,从而增加您的运营成本。 将每个路由拆分为自己的lambda,可以减少运行请求中不需要的代码,从而减轻这种情况。

这是如何运作的?

传统的Express应用看起来像这样:

  const express = require('express'); 
const app = express();

app.get('/',(req,res)=> {
res.send('Hello World')
});

app.listen(3000);

我们实例化一个新的Express应用程序,定义我们的中间件和路由,并侦听指定的端口。 但是我们不想设置实际的HTTP服务器。 由lambda为我们处理。 具体来说,Zeit Now lambda看起来像这样:

 module.exports = (req, res) => { 
res.end('Hello World');
};

我们只导出可以由传统Node HTTP服务器使用的功能。 请注意,在这种情况下, req的类型为IncomingMessage, res的类型为ServerResponse,这与Express Request and Response不同。 那么,如何使Express应用程序变成这种形状? 事实证明,它已经是。 设置Express服务器的另一种方法是使用http包的createServer()函数:

 const express = require('express'); 
const http = require('http');

const app = express();
  app.get('/',(req,res)=> { 
res.send('Hello World')
});
 const server = http.createServer(app); 
server.listen(3000);

这意味着Express app的核心是与@now/node寻找的(IncomingMessage, ServerResponse) => void类型签名相匹配的功能。 因此,我们可以仅导出app而不必在给定端口上启动服务器。

实际上这是什么样的?

src / routes / book.js

  const {Router} = require('express'); 
// ...其他进口商品...
  const router = Router(); 
  //在此处添加您的中间件,以便它也将在开发服务器上使用 
router.use(AuthHandler);
router.use(bodyParser.json());
  //路由中的端点 
router.get('/:bookId',异步(req,res)=> {
const book =等待BookModel.getBook(req.params.bookId);
如果(!book){
返回res.sendStatus(404);
}
 返回res.json(book); 
});
  // ...更多端点... 
  module.exports =路由器; 

在这里,我们实际上定义了路由并添加了端点。 该文件的外观应与传统的Express路由文件基本相同。 请注意,我们将在此处添加所有中间件,因为不会存在一个可以添加通用中间件的总体应用程序文件。

src / lambdas / book.js

  const express = require('express'); 
const mongoose = require('mongoose');
const bookRouter = require('../ routes / book');
const {MONGO_ADDRESS} = process.env;
  //设置数据库连接 
mongoose.connect(MONGO_ADDRESS);
mongoose.Promise = global.Promise;
  const app = express(); 
  //无需仅路由/ book请求; 在now.json中完成 
app.use(bookRouter);
  //仅导出应用程序而不是启动服务器 
module.exports =应用程序;

这是我们lambda的切入点。 我们连接到我们的数据库,创建一个Express应用程序,然后向其中添加路线。 如果您要进行大量设置,则可以将其分解为每个lambda中可重复使用的另一个模块。 请注意,我们将在now.json中指定此lambda仅响应/book路由上的请求,因此我们在这里不需要这样做。

now.json

  { 
“版本”:2
“ name”:“ book-api”,
“建筑物”:[
{“ src”:“ /src/lambdas/book.js”,“ use”:“ @ now / node”}
// ...更多在此处构建-每个路线一个
],
“路线”:[
{“ src”:“ / book /(.*)”,“ dest”:“ / src / lambdas / book.js”}
// ...更多路线。 用户将如何访问“ src”
],
“ env”:{
“ MONGO_ADDRESS”:“ @ mongo-address” // @代表一个秘密
}
}

该文件告诉Zeit Now如何设置我们的Lambda。 builds数组包含所有入口点以及如何处理它们(我们将对每个路径使用@now/node )。 routes数组使用正则表达式将每个请求与给定的lambda匹配。 env对象包含在lambda中使用的环境变量。 @表示通过命令行使用Now进行配置的机密,以便可以对该文件进行源控制。

src / dev.js

  require('dotenv')。config();  //从.env文件加载env vars 
const express = require('express');
const bookRoute = require('./ routes / book');
//导入其他路线...
const {MONGO_ADDRESS} = process.env;
  //设置数据库连接 
mongoose.connect(MONGO_ADDRESS);
mongoose.Promise = global.Promise;
  const app = express(); 
  //在此处添加API中的所有路由 
app.use('/ book',bookRoute);
// ...更多路线在这里...
  app.listen(3000); 

无服务器功能的一个缺点是调试容易。 由于托管公司管理lambda的运行方式,因此很难模仿环境。 此文件设置了用于调试我们的API的基本服务器。 它不是完美的,因此我们需要使用连接信息制作一个.env文件(不要对此进行源控制!)。 请注意,在这种情况下,我们没有Zeit Now可以为我们做路由,因此我们需要指定每个路由的路径。

部署我们的API

该API只是一个框架,可让您大致了解项目结构。 它尚未准备好进行部署,但是为了完整起见,让我们逐步完成使用Zeit Now进行部署的步骤。

  1. 使用NPM安装Zeit Now命令行界面: npm i now -g
  2. 向Now添加一个秘密: now secret add mongo-address 。 在这里, mongo-address是密钥的名称。 现在,如果您还没有登录,将要求您登录到您的帐户。
  3. 部署它! now就运行。 部署后,Now将向您的API输出一个URL。