使用 Node、Express 和 Netlify 构建无服务器 GraphQL API

Avatar of Matthew Ström
Matthew Ström 发布

DigitalOcean 为您旅程的每个阶段提供云产品。立即开始使用 200 美元的免费信用额度!

我一直想构建一个 API,但被其复杂性吓退了。我阅读了很多教程,它们一开始就说“首先,安装这个库、那个库以及另一个库”,却没有解释为什么这些库很重要。在这些方面,我有点像个 卢德分子

好吧,我最近撸起袖子,亲自动手了。我想构建并部署一个简单的只读 API,而且 *该死的*,我不会让一些吓人的依赖项列表和花哨的尖端服务阻止我¹

我发现,在许多教程和项目背后,都有一套简单易懂的工具和技术。我相信,任何人都可以在不到一个小时内,仅用 30 行代码编写和部署自己的只读 API。您不必是高级全栈工程师——只需要基本掌握 JavaScript 以及一些 npm 使用经验即可。

在本文结束时,您将能够部署自己的 API,而无需费心管理服务器。我将列出每个依赖项并解释我们为什么将其纳入。我还将向您介绍一些相关的新概念,并提供深入学习的资源链接。

让我们开始吧!

API 概念概述

有几种常见的方法可以与 API 交互。但让我们首先(非常简要地)解释一下 API 的作用:读取和更新数据。

在过去的 20 年里,出现了一些构建 API 的标准方法。REST(代表 **RE**presentational **S**tate **T**ransfer,即表述性状态转移)是最常见的方法之一。要使用 REST API,您需要通过 URL(例如 api.example.com/rest/books)向服务器发出请求,并期望以 JSON 或 XML 等格式获得书籍列表。要获取单个书籍,我们会返回到服务器上的另一个 URL(例如 api.example.com/rest/books/123),并期望获得书籍 #123 的数据。添加新书籍或更新特定书籍的数据意味着需要更多次访问服务器上的类似的、特定用途的 URL。

这就是我们将要讨论的两个概念的基本思想:**GraphQL** 和 **无服务器**。

GraphQL

执行大量数据获取和更新的应用程序会进行大量 API 调用。复杂的软件(如 Twitter)可能会进行数百次调用才能获取单个页面所需的数据。从少量 URL 中收集正确的数据并对其进行格式化可能是一件令人头疼的事情。2012 年,Facebook 开发人员开始寻找更有效地获取和更新数据的新方法。

他们的关键见解是,在大多数情况下,复杂应用程序中的数据与其他数据存在 *关系*。用户有关注者,关注者本身也是用户,他们都有自己的关注者,而这些关注者发布推文,推文又会收到其他用户的回复。绘制数据之间的关系会产生一个图,而该图可以帮助服务器执行大量巧妙的工作来格式化和发送(或更新)数据,从而为前端开发人员节省时间并减少挫败感。图查询语言,即 GraphQL,由此诞生。

GraphQL 在 URL 和查询的使用方面不同于 REST API 方法。要使用 GraphQL 从我们的 API 获取书籍列表,我们不需要访问特定 URL(例如我们的 api.example.com/graphql/books 示例)。相反,我们通过顶级 URL(在我们的示例中为 api.example.com/graphql)调用 API,并使用 JSON 对象告诉它我们想要获取哪种信息。

{
  books {
    id
    title
    author
  }
}

服务器看到该请求后,会格式化我们的数据并将其以另一个 JSON 对象的形式发送回来。

{
  "books" : [
    {
      "id" : 123
      "title" : "The Greatest CSS Tricks Vol. I"
      "author" : "Chris Coyier"
    }, {
      // ...
    }
  ]
}

Sebastian Scholl 将 GraphQL 与 REST 进行比较,他使用了一个虚构的鸡尾酒会来进行说明,使区别非常清晰。最重要的是:GraphQL 允许我们请求我们想要的确切数据,而 REST 则向我们提供 URL 上所有内容的转储。

概念 2:无服务器

每当我看到“无服务器”这个词时,我就会想到 Chris Watterston 的著名贴纸。

同样,真正“无服务器”的应用程序是不存在的。Chris Coyier 在他的 “无服务器” 文章中很好地总结了这一点。

在我看来,“无服务器”试图表达的是一种管理和支付服务器的新方法。您无需购买单独的服务器。您无需管理它们。您无需扩展它们。您无需平衡它们。您实际上无需为它们负责。您只需支付您使用的部分。

无服务器方法使构建和部署后端应用程序变得更加容易。对于像我这样没有后端开发背景的人来说,尤其如此。与其花时间学习如何配置和维护服务器,我通常会将繁重的工作交给其他人(或者甚至可能是其他事物)。

值得查看 CSS-Tricks 关于无服务器的指南。在 想法页面 上,甚至还有一个关于 构建无服务器 API 的教程链接!

选择我们的工具

如果您浏览该无服务器指南,您会发现有很多工具和资源可以帮助我们构建 API。但是,我们究竟使用哪些工具需要进行一些初步的思考和计划。我将介绍我们将用于只读 API 的两个特定工具。

工具 1:NodeJS 和 Express

再说一次,我在后端 Web 开发方面经验不多。但我遇到的一件事就是 Node.js。你们中的许多人可能已经了解它及其功能,但它本质上是在服务器而不是 Web 浏览器上运行的 JavaScript。对于来自前端开发方面的人来说,Node.js 是完美的,因为我们可以在 JavaScript 中直接工作——包括所有缺点——而无需使用其他后端语言。

Express 是 Node.js 最流行的框架之一。在 React 成为王者之前(各位老铁,你还好吗?),Express 是构建 Web 应用程序的首选。它执行各种实用功能,例如路由、模板化和错误处理。

说实话:像 Express 这样的框架让我感到害怕。但对于简单的 API 来说,Express 非常易于使用和理解。有一个 Express 的官方 GraphQL 助手,还有一个用于构建无服务器应用程序的即插即用库,称为 serverless-http。很酷,对吧?!

工具 2:Netlify 函数

无需维护服务器即可运行应用程序的想法听起来好得令人难以置信。但请看:您不仅可以实现这种现代魔法,而且还可以 *免费* 实现。令人震惊。

Netlify 提供免费套餐,其中包含无服务器功能,每月可提供高达 125,000 次 API 调用。亚马逊提供一项类似的服务,称为 Lambda。在本教程中,我们将坚持使用 Netlify。

Netlify 包括 Netlify Dev,它是 Netlify 平台的 CLI。本质上,它允许我们在功能齐全的生产环境中运行模拟,所有这些都在我们本地机器的安全环境中进行。我们可以使用它来构建和测试我们的无服务器函数,而无需部署它们。

在这一点上,我认为有必要指出,并非所有人都认为在无服务器函数中运行 Express 是一个好主意。 正如 Paul Johnston 所解释的,如果您正在构建可扩展的函数,最好将每个功能分解成自己的单一用途函数。像我这样使用 Express 的方法意味着每次向 API 发送请求时,都需要从头开始启动整个 Express 服务器——效率不高。部署到生产环境需自行承担风险。

开始构建吧!

现在我们已经准备好了工具,可以开始项目了。首先创建一个新文件夹,在终端中导航到该文件夹,然后运行 npm init。 npm 创建 package.json 文件后,我们可以安装所需的依赖项。这些依赖项是

  1. Express
  2. GraphQL 和 express-graphql。 它们允许我们接收和响应 GraphQL 请求。
  3. Bodyparser。 这是一层薄薄的中间件,用于将我们收到的请求转换为 JSON 格式,这是 GraphQL 所期望的。
  4. Serverless-http。 这是一个 Express 的包装器,确保我们的应用程序可以在无服务器平台(如 Netlify)上使用。

就是这样!我们可以使用一条命令安装所有依赖项

npm i express express-graphql graphql body-parser serverless-http

我们还需要将 Netlify Dev 作为全局依赖项安装,以便我们可以将其用作 CLI

npm i -g netlify-cli

文件结构

为了使我们的 API 正确工作,需要几个文件。第一个是 netlify.toml,它应该创建在项目的根目录下。这是一个配置文件,用于告诉 Netlify 如何处理我们的项目。以下是我们需要在文件中定义启动命令、构建命令以及无服务器函数所在位置的内容

[build]


  # This command builds the site
  command = "npm run build"


  # This is the directory that will be deployed
  publish = "build"


  # This is where our functions are located
  functions = "functions"

functions 这行非常重要;它告诉 Netlify 我们将在哪里放置 API 代码。

接下来,让我们在项目的根目录下创建 /functions 文件夹,并在其中创建一个名为 api.js 的新文件。打开它并在顶部添加以下几行,以便我们的依赖项可用并包含在构建中

const express = require("express");
const bodyParser = require("body-parser");
const expressGraphQL = require("express-graphql");
const serverless = require("serverless-http");

设置 Express 只需要几行代码。首先,我们将初始化 Express 并将其包装在 serverless-http 无服务器函数中

const app = express();
module.exports.handler = serverless(app);

这些行初始化 Express,并将其包装在 serverless-http 函数中。module.exports.handler 让 Netlify 知道我们的无服务器函数是 Express 函数。

现在让我们配置 Express 本身

app.use(bodyParser.json());
app.use(
  "/",
  expressGraphQL({
    graphiql: true
  })
);

这两个声明告诉 Express 我们正在运行什么中间件。中间件是我们希望在请求和响应之间发生的事情。在我们的例子中,我们希望使用 bodyparser 解析 JSON,并使用 express-graphql 处理它。express-graphql 的 graphiql:true 配置将为我们提供一个很好的用户界面和测试游乐场。

定义 GraphQL 模式

为了理解请求并格式化响应,GraphQL 需要知道我们的数据是什么样的。如果您使用过数据库,那么您知道这种数据蓝图称为模式。GraphQL 将这种定义良好的模式与类型(即不同种类数据的定义)结合起来发挥其魔力。

我们的模式首先需要一个叫做根查询的东西。这将处理进入我们 API 的任何数据请求。它被称为“根”查询,因为它是在我们 API 的根目录下访问的——例如,api.example.com/graphql

对于这个演示,我们将构建一个 Hello World 示例;根查询应该返回“Hello world”的响应。

因此,我们的 GraphQL API 需要一个用于根查询的模式(由类型组成)。GraphQL 提供了一些现成的类型,包括一个 schema、一个通用的 object² 和一个 string

让我们在导入语句下方添加这些内容

const {
  GraphQLSchema,
  GraphQLObjectType,
  GraphQLString
} = require("graphql");

然后我们将像这样定义我们的模式

const schema = new GraphQLSchema({
  query: new GraphQLObjectType({
    name: 'HelloWorld',
    fields: () => ({ /* we'll put our response here */ })
  })
})

对象中的第一个元素,键为 query,告诉 GraphQL 如何处理根查询。它的值是一个具有以下配置的 GraphQL 对象

  • name – 用于文档目的的引用
  • fields – 定义我们的服务器将返回的数据。在这里有一个函数只返回一个对象可能看起来有点奇怪,但这允许我们在不首先定义它们的情况下使用文件中其他地方定义的变量和函数³
const schema = new GraphQLSchema({
  query: new GraphQLObjectType({
    name: "HelloWorld",
    fields: () => ({
      message: {
        type: GraphQLString,
        resolve: () => "Hello World",
      },
    }),
  }),
});

fields 函数返回一个对象,到目前为止我们的模式只有一个 message 字段。我们希望返回的 message 是一个字符串,因此我们将它的类型指定为 GraphQLString。resolve 函数由我们的服务器运行以生成我们想要的响应。在这种情况下,我们只返回“Hello World”,但在更复杂的应用程序中,我们可能会使用此函数访问我们的数据库并检索一些数据。

这就是我们的模式!我们需要将它告诉我们的 Express 服务器,所以让我们打开 api.js 并确保 Express 配置更新为以下内容

app.use(
  "/",
  expressGraphQL({
    schema: schema,
    graphiql: true
  })
);

在本地运行服务器

信不信由你,我们已经准备好启动服务器了!在终端中从项目的根文件夹运行 netlify dev。Netlify Dev 将读取 netlify.toml 配置,打包您的 api.js 函数,并使其在本地可用。如果一切按计划进行,您将看到类似“服务器现已在 http://localhost:8888 上启动”的消息。

如果您像我第一次那样访问 localhost:8888,您可能会有点失望地收到 404 错误。

但不要害怕!Netlify 正在运行该函数,只是在与您预期不同的目录中,即 /.netlify/functions。因此,如果您访问 localhost:8888/.netlify/functions/api,您应该会看到预期的 GraphiQL 界面。成功!

现在,这才像话!

我们得到的屏幕是 GraphiQL 游乐场,我们可以用它来测试 API。首先,清除左侧窗格中的注释,并将其替换为以下内容

{
  message
}

这可能看起来有点……赤裸裸的……但您刚刚编写了一个 GraphQL 查询!我们说的是我们想查看在 api.js 中定义的 message 字段。单击“运行”按钮,在右侧,您将看到以下内容

{
  "data": {
    "message": "Hello World"
  }
}

我不知道你怎么样,但我第一次做到这一点时,兴奋地挥了挥拳头。我们构建了一个 API!

额外内容:重定向请求

在学习 Netlify 的无服务器函数 时,我遇到的一个问题是它们运行在 /.netlify/functions 路径下。输入或记住它并不理想,我差点就换了其他解决方案。但事实证明,在 Netlfiy 上运行和部署时,您可以轻松地重定向请求。只需在项目的根目录下创建一个名为 _redirects(无需扩展名)的文件,并在其中添加以下行即可

/api /.netlify/functions/api 200!

这告诉 Netlify 任何访问 yoursite.com/api 的流量都应该发送到 /.netlify/functions/api200! 部分指示服务器发送 200 状态代码(表示一切正常)。

部署 API

要部署项目,我们需要将源代码连接到 Netlfiy。我将我的代码托管在一个 GitHub 仓库中,这允许持续部署。

将仓库连接到 Netlfiy 后,其余操作都是自动的:代码会被处理并部署为无服务器函数!您可以登录 Netlify 仪表板以查看任何函数的日志。

总结

就这样,我们能够使用几行 JavaScript 代码和一些简单的配置创建了一个使用 GraphQL 的无服务器 API。而且,我们可以免费部署。

可能性是无限的。也许您想创建自己的个人知识库,或一个用于提供 设计令牌 的工具。也许您想尝试创建自己的 PokéAPI。或者,也许您对使用 GraphQL 感兴趣。

无论您创建什么,这些类型的技术每天都在变得越来越容易获得。能够使用一些最现代的工具和技术而无需深入了解后端技术,这令人兴奋。

如果您想查看此项目的完整源代码,它可以在 GitHub 上找到

本教程中的一些代码改编自 Web Dev Simplified 的 “40 分钟学会 GraphQL” 文章。这是一个深入了解 GraphQL 的极佳资源。但是,它也侧重于更传统的服务器端 Express。


  1. 如果你想查看我探索的完整结果,我已经写了一篇配套文章,名为“实践中的设计 API”,在我的网站上。
  2. 你需要一个特殊的 GraphQL 对象,而不是一个普通的带花括号的 JavaScript 对象的原因,超出了本教程的范围。请记住,GraphQL 是一台经过精心调整的机器,它使用这些专门的类型来实现快速和弹性。
  3. 作用域和提升是 JavaScript 中一些比较令人困惑的话题。MDN 有一个很好的入门指南,值得一看。