如果您以前没有使用过 Gatsby,请阅读一下为什么它 在所有重要的方面都很快,如果您以前没有使用过 FaunaDB ,您将获得额外的享受。如果您想将静态网站打造为完整的 Jamstack 应用程序,这就是适合您的后端解决方案!
本教程将重点介绍使用 FaunaDB 为 Gatsby 博客提供评论系统的操作。该 应用程序 附带允许用户在您的帖子中发表评论的输入字段,以及一个管理区域,供您批准或删除评论,然后才会在每个帖子中显示。身份验证由 Netlify 的 Identity 小部件提供,所有这些都使用 Netlify 无服务器函数和 Apollo/GraphQL API 结合在一起,将数据推送到 FaunaDB 数据库集合中。

我选择 FaunaDB 作为数据库的原因有很多。首先,它有非常慷慨的免费层!非常适合那些需要后端的小型项目,它对 GraphQL 查询提供原生支持,并具有一些非常强大的索引功能!
… 引用创建者的话:
无论您使用哪个堆栈,或将您的应用程序部署到何处,FaunaDB 都能为您提供通过您熟悉的 API 轻松、低延迟、可靠地访问您的数据的途径
您可以查看完成的评论应用程序 这里。
入门
要开始,请在 https://github.com/PaulieScanlon/fauna-gatsby-comments 克隆仓库
或者
git clone https://github.com/PaulieScanlon/fauna-gatsby-comments.git
然后安装所有依赖项
npm install
也 cd 到 functions/apollo-graphql 并安装 Netlify 函数的依赖项
npm install
这是一个单独的包,有自己的依赖项,您稍后将使用它。
我们还需要安装 Netlify CLI,您稍后也会用到它
npm install netlify-cli -g
现在,让我们添加三个不在仓库中的新文件。
在您项目的根目录下创建 .env .env.development 和 .env.production
将以下内容添加到 .env
GATSBY_FAUNA_DB =
GATSBY_FAUNA_COLLECTION =
将以下内容添加到 .env.development
GATSBY_FAUNA_DB =
GATSBY_FAUNA_COLLECTION =
GATSBY_SHOW_SIGN_UP = true
GATSBY_ADMIN_ID =
将以下内容添加到 .env.production
GATSBY_FAUNA_DB =
GATSBY_FAUNA_COLLECTION =
GATSBY_SHOW_SIGN_UP = false
GATSBY_ADMIN_ID =
您稍后会回到这里,但如果您想知道
GATSBY_FAUNA_DB是您数据库的 FaunaDB 密钥GATSBY_FAUNA_COLLECTION是 FaunaDB 集合名称GATSBY_SHOW_SIGN_UP用于在网站处于生产环境时隐藏“注册”按钮GATSBY_ADMIN_ID是由 Netlify Identity 为您生成的用户名
如果您是好奇的人,可以通过运行 gatsby develop 或 yarn develop 来体验应用程序,然后在浏览器中导航到 https://:8000。
FaunaDB
现在让我们开始吧,但在编写任何操作之前,请访问 https://fauna.com/ 并注册!
数据库和集合
- 通过点击 新建数据库 创建一个新的数据库
- 命名数据库:我将演示数据库命名为
fauna-gatsby-comments - 通过点击 新建集合 创建一个新的集合
- 命名集合:我将演示集合命名为
demo-blog-comments

服务器密钥
现在,您需要设置一个服务器密钥。转到 安全性
- 通过点击 新建密钥 创建一个新的密钥
- 选择您希望密钥应用于的数据库,例如
fauna-gatsby-comments - 将 角色 设置为 管理员
- 命名服务器密钥:我将演示密钥命名为
demo-blog-server-key

环境变量第 1 部分
复制服务器密钥并将其添加到 .env.development、.env.production 和 .env 中的 GATSBY_FAUNA_DB。
您还需要将集合名称添加到 .env.development、.env.production 和 .env 中的 GATSBY_FAUNA_COLLECTION。
将这些值添加到 .env 只是为了让您可以测试您的开发 FaunaDB 操作,您将在下一步中进行。
让我们从创建一个评论开始,回到 boop.js
// boop.js
...
// CREATE COMMENT
createComment: async () => {
const slug = "/posts/some-post"
const name = "some name"
const comment = "some comment"
const results = await client.query(
q.Create(q.Collection(COLLECTION_NAME), {
data: {
isApproved: false,
slug: slug,
date: new Date().toString(),
name: name,
comment: comment,
},
})
)
console.log(JSON.stringify(results, null, 2))
return {
commentId: results.ref.id,
}
},
...
此函数的细分如下:
q是faunadb.query的实例Create是 FaunaDB 方法,用于在集合中创建条目Collection是数据库中用于存储数据的区域。它将集合名称作为第一个参数,数据对象作为第二个参数。
第二个参数是您需要驱动应用程序评论系统的数据形状。
现在,您将硬编码 slug、 name 和 comment ,但在最终的应用程序中,这些值将由帖子页面上的输入表单捕获并通过参数传递进来
形状的细分如下:
isApproved是评论的状态,默认情况下为 false,直到我们在管理页面中批准它slug是评论被写入的帖子的路径date是评论被写入的时间戳name是用户在评论表单中输入的名称comment是用户在评论表单中输入的评论
当您(或用户)创建评论时,您并不真正关心处理响应,因为就用户而言,他们只会在评论成功或失败时看到成功或错误消息。
在用户发布评论后,它将进入您的管理队列,直到您批准它,但如果您确实想返回一些内容,您可以通过从 createComment 函数中返回一些内容来在 UI 中显示它。
创建评论
如果您已经硬编码了 slug、 name 和 comment ,现在可以在 CLI 中运行以下代码
node boop createComment
如果一切正常,您应该在终端中看到新评论的日志。
{
"ref": {
"@ref": {
"id": "263413122555970050",
"collection": {
"@ref": {
"id": "demo-blog-comments",
"collection": {
"@ref": {
"id": "collections"
}
}
}
}
}
},
"ts": 1587469179600000,
"data": {
"isApproved": false,
"slug": "/posts/some-post",
"date": "Tue Apr 21 2020 12:39:39 GMT+0100 (British Summer Time)",
"name": "some name",
"comment": "some comment"
}
}
{ commentId: '263413122555970050' }
如果您转到 FaunaDB 中的 集合 ,您应该在集合中看到您的新条目。

您需要在开发过程中创建更多评论,因此请更改 name 和 comment 的硬编码值,然后再次运行以下代码。
node boop createComment
重复此操作几次,以便最终在数据库中存储至少三个新评论,您将在下一步中使用它们。
按 ID 删除评论
现在您已经可以创建评论,您还需要能够删除评论。
通过添加您在上面创建的评论之一的 commentId ,可以将其从数据库中删除。 commentId 是 ref.@ref 对象中的 id
同样,您并不真正关心返回值,但如果您想在 UI 中显示它,可以通过从 deleteCommentById 函数中返回一些内容来做到这一点。
// boop.js
...
// DELETE COMMENT
deleteCommentById: async () => {
const commentId = "263413122555970050";
const results = await client.query(
q.Delete(q.Ref(q.Collection(COLLECTION_NAME), commentId))
);
console.log(JSON.stringify(results, null, 2));
return {
commentId: results.ref.id,
};
},
...
此函数的细分如下
client是 FaunaDB 客户端实例query是用于从 FaunaDB 获取数据的函数q是faunadb.query的实例Delete是 FaunaDB 删除方法,用于从集合中删除条目Ref是用于标识条目的唯一 FaunaDB 引用Collection是数据库中存储数据的区域
如果您已经硬编码了 commentId ,现在可以在 CLI 中运行以下代码
node boop deleteCommentById
如果您返回 FaunaDB 中的 集合 ,您应该看到该条目不再存在于集合中
索引
接下来,您将在 FaunaDB 中创建一个 **索引**。
**索引** 允许您使用特定术语查询数据库,并定义要返回的特定数据形状。
在使用 GraphQL 和/或 TypeScript 时,这非常强大,因为您可以使用 FaunaDB 索引来返回 *仅* 您需要的 数据,并以可预测的形状。这使得在 GraphQL 和/或 TypeScript 中对数据类型进行响应成为一件轻而易举的事……我曾参与过多个应用程序的开发,这些应用程序只是返回一个巨大的无用值对象,这不可避免地会导致应用程序中的错误。令人沮丧!

- 转到 **索引**,然后点击 **新建索引**
- 为索引命名:我将此索引命名为
get-all-comments - 将 **源集合** 设置为之前设置的集合的名称
如上所述,当您使用此索引查询数据库时,您可以告诉 FaunaDB 您想要返回的条目哪些部分。
您可以通过添加“值”来实现这一点,但请注意,要完全按照下面显示的那样输入值,因为(在 FaunaDB 免费层级上)您在创建索引后无法修改它们,因此如果出现错误,您将不得不删除索引并重新开始……令人沮丧!
您需要添加的值如下:
refdata.isApproveddata.slugdata.datedata.namedata.comment
添加完所有值后,您可以点击 **保存**。
获取所有评论
// boop.js
...
// GET ALL COMMENTS
getAllComments: async () => {
const results = await client.query(
q.Paginate(q.Match(q.Index("get-all-comments")))
);
console.log(JSON.stringify(results, null, 2));
return results.data.map(([ref, isApproved, slug, date, name, comment]) => ({
commentId: ref.id,
isApproved,
slug,
date,
name,
comment,
}));
},
...
此函数的细分如下
client是 FaunaDB 客户端实例query是用于从 FaunaDB 获取数据的函数q是faunadb.query的实例Paginate对响应进行分页Match返回匹配的结果Index是您刚刚创建的 *索引* 的名称
此处返回结果的形状是一个数组,其形状与您在 *索引* “值”中定义的形状相同。
如果您运行以下代码,您应该会看到之前创建的所有评论的列表。
node boop getAllComments
按 slug 获取评论
您将采取与上述类似的方法,但这次创建一个新的 *索引*,允许您以不同的方式查询 FaunaDB。这里的主要区别是,当您 **get-comments-by-slug** 时,您需要告诉 FaunaDB 关于这个特定的 **术语**,您可以通过在 *术语* 字段中添加 data.slug 来实现。

- 转到 **索引**,然后点击 **新建索引**
- 为索引命名,我将此索引命名为
get-comments-by-slug - 将 **源集合** 设置为之前设置的集合的名称
- 在术语字段中添加
data.slug
您需要添加的值如下:
refdata.isApproveddata.slugdata.datedata.namedata.comment
添加完所有值后,您可以点击保存。
// boop.js
...
// GET COMMENT BY SLUG
getCommentsBySlug: async () => {
const slug = "/posts/some-post";
const results = await client.query(
q.Paginate(q.Match(q.Index("get-comments-by-slug"), slug))
);
console.log(JSON.stringify(results, null, 2));
return results.data.map(([ref, isApproved, slug, date, name, comment]) => ({
commentId: ref.id,
isApproved,
slug,
date,
name,
comment,
}));
},
...
此函数的细分如下
client是 FaunaDB 客户端实例query是用于从 FaunaDB 获取数据的函数q是faunadb.query的实例Paginate对响应进行分页Match返回匹配的结果Index是您刚刚创建的 *索引* 的名称
此处返回结果的形状是一个数组,其形状与您在 *索引* “值”中定义的形状相同,您可以按照上述相同的方式创建此形状,并确保为术语添加一个值。同样,请注意谨慎输入这些值。
如果您运行以下代码,您应该会看到之前创建的所有评论的列表,但针对的是特定的 slug
node boop getCommentsBySlug
按 ID 批准评论
创建评论时,您会手动将 isApproved 值设置为 false。这将阻止评论在您批准之前显示在应用程序中。
现在,您需要创建一个函数来执行此操作,但您需要硬编码一个 commentId。使用您之前创建的评论之一的 commentId
// boop.js
...
// APPROVE COMMENT BY ID
approveCommentById: async () => {
const commentId = '263413122555970050'
const results = await client.query(
q.Update(q.Ref(q.Collection(COLLECTION_NAME), commentId), {
data: {
isApproved: true,
},
})
);
console.log(JSON.stringify(results, null, 2));
return {
isApproved: results.isApproved,
};
},
...
此函数的细分如下
client是 FaunaDB 客户端实例query是用于从 FaunaDB 获取数据的函数q是faunadb.query的实例Update是 FaundaDB 中用于更新条目的方法Ref是用于标识条目的唯一 FaunaDB 引用Collection是数据库中存储数据的区域
如果您已经硬编码了 commentId ,现在可以在 CLI 中运行以下代码
node boop approveCommentById
如果您再次运行 getCommentsBySlug,您现在应该会看到您为其硬编码了 commentId 的条目的 isApproved 状态已更改为 true。
node boop getCommentsBySlug
这些是管理应用程序中数据的所需的所有操作。
在存储库中,如果您查看 apollo-graphql.js(位于 functions/apollo-graphql 中),您将看到所有上述操作。如前所述,硬编码的值将被 args 替换,这些值是从应用程序的各个部分传递进来的。
Netlify
假设您已完成 Netlify 注册流程或已拥有 Netlify 帐户,您现在可以将演示应用程序推送到您的 GitHub 帐户。
为此,您需要在本地初始化 Git,添加一个远程仓库,并将演示存储库推送到上游,然后再继续。
您现在应该能够将存储库链接到 Netlify 的 持续部署。
如果您点击 Netlify 仪表板上的“从 Git 创建新站点”按钮,您可以授权访问您的 GitHub 帐户,并选择 gatsby-fauna-comments 存储库以启用 Netlify 的持续部署。您需要至少部署一次,以便我们拥有应用程序的公共 URL。
URL 类似于 https://ecstatic-lewin-b1bd17.netlify.app,但您可以随意重命名它,并记下 URL,因为您将在稍后提到的 Netlify Identity 步骤中用到它。

环境变量第 2 部分
在之前的步骤中,您将 FaunaDB 数据库密钥和集合名称添加到您的 .env 文件(s) 中。您还需要将它们添加到 Netlify 的 **环境变量** 中。
- 从 Netlify 导航中转到设置
- 点击 **构建和部署**
- 选择 **环境** 或向下滚动,直到看到 **环境变量**
- 点击 **编辑变量**
继续添加以下内容:
GATSBY_SHOW_SIGN_UP = false
GATSBY_FAUNA_DB = you FaunaDB secret key
GATSBY_FAUNA_COLLECTION = you FaunaDB collection name
在这里,您还需要修改 **敏感变量策略**,选择 *无限制部署*
Netlify Identity 小部件
之前提到过,创建评论时,isApproved 值将被设置为 false,这将阻止评论在您(管理员)批准之前显示在博文上。要成为管理员,您需要创建一个 *身份*。
您可以使用 Netlify Identity 小部件 来实现这一点。
如果您已完成上述持续部署步骤,您可以从 Netlify 导航中转到身份页面。

您目前不会在这里看到任何用户,因此让我们使用应用程序来连接这些点,但在此之前,请确保点击 **启用身份**
在您继续之前,我想指出,您将从现在开始使用 netlify dev 而不是 gatsby develop 或 yarn develop。这是因为您将在应用程序中使用一些“特殊”的 Netlify 方法,并且使用 netlify dev 启动服务器是启动您将使用的各种流程所必需的。
- 使用
netlify dev启动应用程序 - 导航到
https://:8888/admin/ - 点击标题中的 **注册** 按钮
您还需要将 Netlify Identity 小部件指向您新部署的应用程序 URL。这是我之前提到的您需要记下的 URL,如果您没有重命名应用程序,它将类似于 https://ecstatic-lewin-b1bd17.netlify.app/,弹出的窗口中会提示您 **设置站点的 URL**。
您现在可以完成必要的注册步骤。
注册后,您会收到一封电子邮件,要求您确认您的身份,确认后,刷新 Netlify 中的身份页面,您应该会看到自己作为用户。
现在是登录时间了,但在您这样做之前,请在 src/components 中找到 Identity.js,并暂时取消第 14 行上的 console.log() 的注释。这将把 Netlify Identity 用户对象记录到控制台中。
- 重新启动本地服务器
- 再次使用
netlify dev启动应用程序 - 点击标题中的 *登录* 按钮
如果一切正常,您应该能够看到 netlifyIdentity.currentUser: 的控制台日志,找到 id 键并复制其值。
将其设置为 .env.production 和 .env.development 中 GATSBY_ADMIN_ID = 的值。
您现在可以安全地删除 Identity.js 中第 14 行上的 console.log(),或者只是再次将其注释掉。
GATSBY_ADMIN_ID = your Netlify Identity user id
…最后
- 重新启动本地服务器
- 再次使用
netlify dev启动应用程序
现在您应该能够以“管理员”身份登录……太棒了!
导航到 https://:8888/admin/ 并登录。
请注意,您现在将使用 **localhost:8888** 进行开发,而不是 **localhost:8000**,后者在 Gatsby 开发中更为常见
在您在部署环境中测试之前,请确保返回 Netlify 的 **环境变量**,并将您的 Netlify Identity 用户 id 添加到环境变量中!
- 从 Netlify 导航中转到设置
- 点击 **构建和部署**
- 选择 **环境** 或向下滚动,直到看到 **环境变量**
- 点击 **编辑变量**
继续添加以下内容:
GATSBY_ADMIN_ID = your Netlify Identity user id
如果你玩一下这个应用程序,并在每个帖子中输入一些评论,然后导航回管理页面,你可以选择批准或删除评论。
当然,只有批准的评论才会显示在任何给定的帖子中,而被删除的评论则会永远消失。
如果你在你的项目中使用了本教程,我很乐意在 @pauliescanlon 听到你的消息。
作者:Paulie Scanlon (@pauliescanlon),前端 React UI 开发人员/UX 工程师:说到底,结构 + 秩序 = 乐趣。
访问 Paulie 的博客:www.paulie.dev
我一直在尝试遵循本教程,从一开始我就遇到了问题。看起来 npm 不是要使用的正确包管理器,npm install 抛出了依赖错误,当我尝试 yarn install 时,它就像魅力一样工作了。
只是把它留在这里,以防有人尝试遵循并卡住了。