使用 React、Serverless 和 Airtable 构建 Jamstack 应用

Avatar of James Quick
James Quick 发布

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

学习的最佳方式是 构建。让我们通过使用 React、Netlify(Serverless)函数和 Airtable 构建一个网站来了解这个热门的新词语 Jamstack。Jamstack 的组成部分之一是静态托管,但这并不意味着网站上的所有内容都必须是静态的。事实上,我们将构建一个具有完整 CRUD 功能的应用程序,就像任何使用更传统服务器端访问的 Web 技术的教程一样。

为什么要选择这些技术?

您可能已经知道这一点,但 Jamstack 中的“JAM”代表 JavaScript、API 和 Markup。这些技术本身并不新鲜,因此 Jamstack 实际上只是一种结合它们的新颖而创造性的方式。您可以在 Jamstack 网站 上了解更多相关信息。

Jamstack 最重要的优势之一是易于部署和托管,这极大地影响了我们正在使用的技术。通过整合 Netlify 函数(用于与 Airtable 进行后端 CRUD 操作),我们将能够将我们的全栈应用程序部署到 Netlify。此过程的简单性是 Jamstack 的魅力所在。

至于数据库,我选择 Airtable 是因为我想要一个 易于上手 的东西。此外,我不想陷入技术数据库细节的泥潭,因此 Airtable 非常适合。以下是 Airtable 的一些优势

  1. 您无需自己部署或托管数据库
  2. 它带有一个类似 Excel 的 GUI 用于查看和编辑数据
  3. 它有一个不错的 JavaScript SDK

我们将构建什么

为了便于后续理解,我们将构建一个应用程序,您可以使用它来跟踪您想学习的在线课程。我个人会学习很多在线课程,有时很难跟上我积压的课程。这个应用程序将允许跟踪这些课程,类似于 Netflix 队列。

 

我学习很多在线课程的原因之一是我自己也制作课程。事实上,我有一个新的课程,您可以学习如何使用 React 和 Netlify(Serverless)函数构建安全且可用于生产环境的 Jamstack 应用程序。我们将涵盖身份验证、在 Airtable 中存储数据、Styled Components、使用 Netlify 进行持续集成等等!立即查看 →

Airtable 设置

首先,让我澄清一下,Airtable 将其数据库称为“基座”。因此,要开始使用 Airtable,我们需要做几件事。

  1. 注册一个免费帐户
  2. 创建一个新的“基座”
  3. 定义一个用于存储课程的新表格

接下来,让我们创建一个新的数据库。我们将登录 Airtable,点击“添加基座”并选择“从头开始”选项。我将我的新基座命名为“JAMstack 演示”,以便将来可以将其用于不同的项目。

接下来,让我们点击基座以将其打开。

您会注意到,这看起来非常类似于 Excel 或 Google Sheets 文档。这对于直接在仪表板中处理数据非常有用。已经创建了一些列,但我们可以添加我们自己的列。以下是我们需要创建的列及其类型

  1. 名称(单行文本)
  2. 链接(单行文本)
  3. 标签(多选)
  4. 已购买(复选框)

趁此机会,我们应该向标签列中添加一些标签。我首先添加了“node”、“react”、“jamstack”和“javascript”。请随意添加任何对您可能感兴趣的课程类型有意义的标签。

我还根据我最喜欢的在线课程在名称列中添加了一些数据行

  1. 构建 20 个 React 应用
  2. 高级 React 安全模式
  3. React 和 Serverless

最后要做的是重命名表格本身。默认情况下,它被称为“表格 1”。我将其重命名为“课程”。

查找 Airtable 凭据

在开始编写代码之前,我们需要从 Airtable 获取几个信息。第一个是您的 API 密钥。获取此密钥的最简单方法是转到 您的帐户页面 并查看“概览”部分。

接下来,我们需要我们刚刚创建的基座的 ID。我建议您前往 Airtable API 页面,因为您将在那里看到基座列表。点击您刚刚创建的基座,您应该会看到列出的基座 ID。Airtable API 的文档非常方便,并提供了有关查找基座 ID 的更详细说明。

最后,我们需要表格的名称。我将其命名为“课程”,但如果您的名称不同,请使用您自己的名称。

项目设置

为了加快速度,我在主要的 存储库 中为我们创建了一个入门项目。您需要执行以下几个步骤才能继续学习

  1. 通过点击分叉按钮分叉 存储库
  2. 将新的存储库克隆到本地
  3. 使用 git checkout starter 检查入门分支

这里已经有许多文件。大多数文件来自一个标准的 create-react-app 应用程序,但有一些例外。还有一个 functions 目录,它将托管我们所有的无服务器函数。最后,还有一个 netlify.toml 配置文件,它告诉 Netlify 我们的无服务器函数位于哪里。此配置中还有一个重定向,简化了我们用于调用函数的路径。稍后将详细介绍。

设置的最后一步是整合环境变量,以便我们可以在无服务器函数中使用它们。为此,请安装 dotenv 包。

npm install dotenv

然后,在存储库的根目录中创建一个 .env 文件,内容如下。请确保使用您之前找到的自己的 API 密钥、基座 ID 和表格名称。

AIRTABLE_API_KEY=<YOUR_API_KEY>
AIRTABLE_BASE_ID=<YOUR_BASE_ID>
AIRTABLE_TABLE_NAME=<YOUR_TABLE_NAME>

现在让我们编写一些代码!

设置无服务器函数

要使用 Netlify 创建无服务器函数,我们需要在 /functions 目录中创建一个 JavaScript 文件。此入门目录中已经包含了一些文件。让我们首先查看 courses.js 文件。

const  formattedReturn  =  require('./formattedReturn');
const  getCourses  =  require('./getCourses');
const  createCourse  =  require('./createCourse');
const  deleteCourse  =  require('./deleteCourse');
const  updateCourse  =  require('./updateCourse');
exports.handler  =  async  (event)  =>  {
  return  formattedReturn(200, 'Hello World');
};

无服务器函数的核心部分是 exports.handler 函数。在这里,我们处理传入的请求并对其进行响应。在本例中,我们接受一个事件参数,我们稍后将使用它。

我们在处理程序内部返回对 formattedReturn 函数的调用,这使得返回状态和主体数据变得更加简单。以下是对该函数的参考。

module.exports  =  (statusCode, body)  =>  {
  return  {
    statusCode,
    body: JSON.stringify(body),
  };
};

还要注意,我们正在导入几个辅助函数来处理与 Airtable 的交互。我们可以根据传入请求的 HTTP 方法决定调用其中的哪一个。

  • HTTP GET → getCourses
  • HTTP POST → createCourse
  • HTTP PUT → updateCourse
  • HTTP DELETE → deleteCourse

让我们更新此函数,以便根据事件参数中的 HTTP 方法调用相应的辅助函数。如果请求与我们期望的方法不匹配,我们可以返回 405 状态代码(方法不允许)。

exports.handler = async (event) => {
  if (event.httpMethod === 'GET') {
    return await getCourses(event);
  } else if (event.httpMethod === 'POST') {
    return await createCourse(event);
  } else if (event.httpMethod === 'PUT') {
    return await updateCourse(event);
  } else if (event.httpMethod === 'DELETE') {
    return await deleteCourse(event);
  } else {
    return formattedReturn(405, {});
  }
};

更新 Airtable 配置文件

由于我们将在每个不同的辅助文件中与 Airtable 进行交互,因此让我们对其进行一次配置并重复使用它。打开 airtable.js 文件。

在本文件中,我们希望获取之前创建的courses表的引用。为此,我们使用 API 密钥和基础 ID 创建对 Airtable 基础的引用。然后,我们使用该基础获取对表的引用并将其导出。

require('dotenv').config();
var Airtable = require('airtable');
var base = new Airtable({ apiKey: process.env.AIRTABLE_API_KEY }).base(
  process.env.AIRTABLE_BASE_ID
);
const table = base(process.env.AIRTABLE_TABLE_NAME);
module.exports = { table };

获取课程

在 Airtable 配置就位后,我们现在可以打开getCourses.js文件并通过调用table.select().firstPage()从我们的表中检索课程。Airtable API 使用分页,因此在这种情况下,我们指定我们想要记录的第一页(默认情况下为 20 条记录)。

const courses = await table.select().firstPage();
return formattedReturn(200, courses);

就像任何异步/等待调用一样,我们需要处理错误。让我们用 try/catch 将此代码段包围起来。

try {
  const courses = await table.select().firstPage();
  return formattedReturn(200, courses);
} catch (err) {
  console.error(err);
  return formattedReturn(500, {});
}

Airtable 在其记录中返回了许多额外的信息。我更喜欢只使用记录 ID 和我们在上面创建的每个表列的值来简化这些记录。这些值位于fields属性中。为此,我使用数组映射来格式化我想要的数据。

const { table } = require('./airtable');
const formattedReturn = require('./formattedReturn');
module.exports = async (event) => {
  try {
    const courses = await table.select().firstPage();
    const formattedCourses = courses.map((course) => ({
      id: course.id,
      ...course.fields,
    }));
    return formattedReturn(200, formattedCourses);
  } catch (err) {
    console.error(err);
    return formattedReturn(500, {});
  }
};

我们如何测试它?嗯,netlify-cli为我们提供了netlify dev命令来在本地运行我们的无服务器函数(以及我们的前端)。首先,安装 CLI

npm install -g netlify-cli

然后,在目录内运行netlify dev命令。

这个漂亮的命令为我们做了几件事

  • 运行无服务器函数
  • 为您的站点运行 Web 服务器
  • 创建前端和无服务器函数在端口 8888 上相互通信的代理。

让我们打开以下 URL 来查看是否有效

由于netlify.toml文件中的重定向配置,我们能够对我们的 API 使用/api/*

如果成功,我们应该会看到数据显示在浏览器中。

创建课程

让我们通过打开createCourse.js文件来添加创建课程的功能。我们需要从传入的 POST 主体中获取属性,并使用它们通过调用table.create()来创建新记录。

传入的event.body以常规字符串形式出现,这意味着我们需要对其进行解析以获取 JavaScript 对象。

const fields = JSON.parse(event.body);

然后,我们使用这些字段来创建一个新的课程。请注意,create()函数接受一个数组,这允许我们一次创建多个记录。

const createdCourse = await table.create([{ fields }]);

然后,我们可以返回createdCourse

return formattedReturn(200, createdCourse);

当然,我们应该用 try/catch 将内容包装起来

const { table } = require('./airtable');
const formattedReturn = require('./formattedReturn');
module.exports = async (event) => {
  const fields = JSON.parse(event.body);
  try {
    const createdCourse = await table.create([{ fields }]);
    return formattedReturn(200, createdCourse);
  } catch (err) {
    console.error(err);
    return formattedReturn(500, {});
  }
};

由于我们无法直接在浏览器 Web 地址中执行 POST、PUT 或 DELETE(就像我们对 GET 所做的那样),因此从现在开始我们需要使用单独的工具来测试我们的端点。我更喜欢Postman,但我听说Insomnia也不错。

在 Postman 中,我需要以下配置。

  • url: localhost:8888/api/courses
  • method: POST
  • body: 包含namelinktags的 JSON 对象

运行请求后,我们应该会看到返回了新的课程记录。

我们还可以检查 Airtable GUI 以查看新记录。

提示:复制粘贴新记录中的 ID 以在接下来的两个函数中使用。

更新课程

现在,让我们转向更新现有课程。从传入的请求主体中,我们需要记录的id以及其他字段值。

我们可以使用对象解构来专门获取 id 值,如下所示

const {id} = JSON.parse(event.body);

然后,我们可以使用扩展运算符获取其余的值并将其分配给名为fields的变量

const {id, ...fields} = JSON.parse(event.body);

在那里,我们调用update()函数,该函数接受要更新的对象数组(每个对象都有一个idfields属性)

const updatedCourse = await table.update([{id, fields}]);

以下是将所有这些放在一起的完整文件

const { table } = require('./airtable');
const formattedReturn = require('./formattedReturn');
module.exports = async (event) => {
  const { id, ...fields } = JSON.parse(event.body);
  try {
    const updatedCourse = await table.update([{ id, fields }]);
    return formattedReturn(200, updatedCourse);
  } catch (err) {
    console.error(err);
    return formattedReturn(500, {});
  }
};

要测试它,我们将回到 Postman 进行 PUT 请求

  • url: localhost:8888/api/courses
  • method: PUT
  • body: 包含id(我们刚刚创建的课程的id)以及我们想要更新的字段(namelinktags)的 JSON 对象

我决定在课程更新后在其名称后附加“已更新!!!”。

我们也可以在 Airtable GUI 中看到更改。

删除课程

最后,我们需要添加删除功能。打开deleteCourse.js文件。我们将需要从请求主体中获取 id 并使用它来调用destroy()函数。

const { id } = JSON.parse(event.body);
const deletedCourse = await table.destroy(id);

最终文件如下所示

const { table } = require('./airtable');
const formattedReturn = require('./formattedReturn');
module.exports = async (event) => {
  const { id } = JSON.parse(event.body);
  try {
    const deletedCourse = await table.destroy(id);
    return formattedReturn(200, deletedCourse);
  } catch (err) {
    console.error(err);
    return formattedReturn(500, {});
  }
};

以下是 Postman 中删除请求的配置。

  • url: localhost:8888/api/courses
  • method: DELETE
  • body: 包含 id(与我们刚刚更新的课程的 id 相同)的 JSON 对象

当然,我们可以通过查看 Airtable GUI 来仔细检查记录是否已删除。

在 React 中显示课程列表

哇,我们已经构建了整个后端!现在,让我们继续前端。大部分代码已经编写好了。我们只需要编写与无服务器函数交互的部分。让我们从显示课程列表开始。

打开App.js文件并找到loadCourses函数。在里面,我们需要调用我们的无服务器函数来检索课程列表。对于此应用程序,我们将使用内置的fetch发出 HTTP 请求。

感谢netlify dev命令,我们可以使用相对于端点的路径发出请求。最棒的是,这意味着我们无需在部署应用程序后进行任何更改!

const res = await fetch('/api/courses');
const courses = await res.json();

然后,将课程列表存储在courses状态变量中。

setCourses(courses)

将它们放在一起并用 try/catch 包裹起来

const loadCourses = async () => {
  try {
    const res = await fetch('/api/courses');
    const courses = await res.json();
    setCourses(courses);
  } catch (error) {
    console.error(error);
  }
};

在浏览器中打开localhost:8888,我们应该会看到我们的课程列表。

在 React 中添加课程

现在我们有了查看课程的功能,我们需要创建新课程的功能。打开CourseForm.js文件并查找submitCourse函数。在这里,我们需要向 API 发出 POST 请求并在主体中发送表单的输入。

JavaScript Fetch API 默认情况下发出 GET 请求,因此要发送 POST,我们需要传递一个包含请求的配置对象。此options对象将具有以下两个属性。

  1. method → POST
  2. body → 输入数据的字符串化版本
await fetch('/api/courses', {
  method: 'POST',
  body: JSON.stringify({
    name,
    link,
    tags,
  }),
});

然后,用 try/catch 将调用包围起来,整个函数如下所示

const submitCourse = async (e) => {
  e.preventDefault();
  try {
    await fetch('/api/courses', {
      method: 'POST',
      body: JSON.stringify({
        name,
        link,
        tags,
      }),
    });
    resetForm();
    courseAdded();
  } catch (err) {
    console.error(err);
  }
};

在浏览器中测试它。填写表单并提交。

提交表单后,表单应重置,课程列表应更新为包含新添加的课程。

在 React 中更新已购买的课程

课程列表分为两个不同的部分:一个包含已购买的课程,另一个包含未购买的课程。我们可以添加标记课程为“已购买”的功能,以便它显示在正确的部分。为此,我们将向 API 发送 PUT 请求。

打开Course.js文件并查找markCoursePurchased函数。在这里,我们将发出 PUT 请求,并包含课程的 id 以及将 purchased 属性设置为 true 的课程属性。我们可以通过传递课程的所有属性(使用扩展运算符)然后将purchased属性覆盖为 true 来实现。

const markCoursePurchased = async () => {
  try {
    await fetch('/api/courses', {
      method: 'PUT',
      body: JSON.stringify({ ...course, purchased: true }),
    });
    refreshCourses();
  } catch (err) {
    console.error(err);
  }
};

要测试它,请单击按钮将其中一个课程标记为已购买,课程列表应更新为在已购买部分显示该课程。

在 React 中删除课程

接下来,按照我们的 CRUD 模型,我们将添加删除课程的功能。为此,请找到我们刚刚编辑的 Course.js 文件中的 deleteCourse 函数。我们需要向 API 发出 DELETE 请求,并传递要删除的课程的 ID。

const deleteCourse = async () => {
  try {
    await fetch('/api/courses', {
      method: 'DELETE',
      body: JSON.stringify({ id: course.id }),
    });
    refreshCourses();
  } catch (err) {
    console.error(err);
  }
};

要测试此功能,请点击课程旁边的“删除”按钮,课程应该会从列表中消失。我们还可以通过检查 Airtable 仪表盘来验证它是否完全消失了。

部署到 Netlify

现在,我们已经在前端和后端实现了所有需要的 CRUD 功能,是时候将它部署到 Netlify 了。希望您和我一样,对这有多么容易感到兴奋。在进入部署之前,请确保所有内容都已推送到 GitHub。

如果您没有 Netlify 账户,则需要创建一个(就像 Airtable 一样,它是免费的)。然后,在仪表盘中,点击“从 Git 创建新站点”选项。选择 GitHub,对其进行身份验证,然后选择项目仓库。

接下来,我们需要告诉 Netlify 从哪个分支进行部署。这里我们有两个选择。

  1. 使用我们一直在使用的 starter 分支
  2. 选择包含代码最终版本的 master 分支

目前,我建议选择 starter 分支以确保代码能够正常工作。然后,我们需要选择一个构建应用程序的命令和一个用于提供服务的发布目录。

  1. 构建命令:npm run build
  2. 发布目录:build

Netlify 最近发布了一个更新,在构建过程中将 React 警告视为错误,这可能会导致构建失败。我已经将构建命令更新为 CI = npm run build 以解决此问题。

最后,点击“显示高级”按钮,并添加环境变量。这些变量应该与我们创建的本地 .env 文件中的变量完全相同。

站点应该会自动开始构建。

我们可以在 Netlify 选项卡中点击“部署”选项卡,并跟踪构建进度,尽管它速度很快。完成后,我们闪亮的新应用程序就会部署出来,供全世界观看!

欢迎来到 Jamstack!

Jamstack 是一个有趣的新领域。我喜欢它,因为它使构建和托管像这样的功能齐全的全栈应用程序变得非常简单。我喜欢 Jamstack 使我们成为强大而全能的前端开发者

我希望您也看到了我们在这里使用的技术组合带来的相同的力量和便捷性。再次强调,Jamstack 并不强制要求我们使用 Airtable、React 或 Netlify,但我们可以使用它们,而且它们都是免费且易于设置的。查看 Chris 的无服务器站点,以获取大量其他服务、资源和在 Jamstack 中工作的想法。欢迎在下面的评论中提出您的问题和反馈!