带 FaunaDB 的 API 服务器
FaunaDB 自称为“现代应用程序的数据 API”。它是一个带有 GraphQL 接口的数据库,使您能够使用 GraphQL 与其交互。由于我们通过 HTTP 请求与其通信,我们无需管理连接,这非常适合无服务器应用程序。
本教程假定您拥有 FaunaDB 和 Deno Deploy 账户,已安装 Deno Deploy CLI,并具备 GraphQL 的基本知识。
概览 跳转到标题
在本教程中,我们将构建一个小型语录 API,包含插入和检索语录的端点。稍后将使用 FaunaDB 来持久化这些语录。
让我们从定义 API 端点开始。
# A POST request to the endpoint should insert the quote to the list.
POST /quotes/
# Body of the request.
{
"quote": "Don't judge each day by the harvest you reap but by the seeds that you plant.",
"author": "Robert Louis Stevenson"
}
# A GET request to the endpoint should return all the quotes from the database.
GET /quotes/
# Response of the request.
{
"quotes": [
{
"quote": "Don't judge each day by the harvest you reap but by the seeds that you plant.",
"author": "Robert Louis Stevenson"
}
]
}
既然我们了解了端点应该如何运行,接下来就构建它。
构建 API 端点 跳转到标题
首先,创建一个名为 quotes.ts
的文件,并粘贴以下内容。
阅读代码中的注释以了解其作用。
import {
json,
serve,
validateRequest,
} from "https://deno.land/x/sift@0.6.0/mod.ts";
serve({
"/quotes": handleQuotes,
});
// To get started, let's just use a global array of quotes.
const quotes = [
{
quote: "Those who can imagine anything, can create the impossible.",
author: "Alan Turing",
},
{
quote: "Any sufficiently advanced technology is equivalent to magic.",
author: "Arthur C. Clarke",
},
];
async function handleQuotes(request: Request) {
// Make sure the request is a GET request.
const { error } = await validateRequest(request, {
GET: {},
});
// validateRequest populates the error if the request doesn't meet
// the schema we defined.
if (error) {
return json({ error: error.message }, { status: error.status });
}
// Return all the quotes.
return json({ quotes });
}
使用 Deno CLI 运行上述程序。
deno run --allow-net=:8000 ./path/to/quotes.ts
# Listening on http://0.0.0.0:8000/
并使用 curl 请求该端点以查看一些语录。
curl http://127.0.0.1:8000/quotes
# {"quotes":[
# {"quote":"Those who can imagine anything, can create the impossible.", "author":"Alan Turing"},
# {"quote":"Any sufficiently advanced technology is equivalent to magic.","author":"Arthur C. Clarke"}
# ]}
接下来处理 POST 请求。
更新 validateRequest
函数,确保 POST 请求遵循所提供的主体方案。
- const { error } = await validateRequest(request, {
+ const { error, body } = await validateRequest(request, {
GET: {},
+ POST: {
+ body: ["quote", "author"]
+ }
});
通过使用以下代码更新 handleQuotes
函数来处理 POST 请求。
async function handleQuotes(request: Request) {
const { error, body } = await validateRequest(request, {
GET: {},
POST: {
body: ["quote", "author"],
},
});
if (error) {
return json({ error: error.message }, { status: error.status });
}
+ // Handle POST requests.
+ if (request.method === "POST") {
+ const { quote, author } = body as { quote: string; author: string };
+ quotes.push({ quote, author });
+ return json({ quote, author }, { status: 201 });
+ }
return json({ quotes });
}
让我们通过插入一些数据来测试它。
curl --dump-header - --request POST --data '{"quote": "A program that has not been tested does not work.", "author": "Bjarne Stroustrup"}' http://127.0.0.1:8000/quotes
输出可能看起来像下面这样。
HTTP/1.1 201 Created
transfer-encoding: chunked
content-type: application/json; charset=utf-8
{"quote":"A program that has not been tested does not work.","author":"Bjarne Stroustrup"}
太棒了!我们构建了 API 端点,并且它按预期工作。由于数据存储在内存中,重启后会丢失。让我们使用 FaunaDB 来持久化我们的语录。
使用 FaunaDB 进行持久化 跳转到标题
让我们使用 GraphQL Schema 定义数据库模式。
# We're creating a new type named `Quote` to represent a quote and its author.
type Quote {
quote: String!
author: String!
}
type Query {
# A new field in the Query operation to retrieve all quotes.
allQuotes: [Quote!]
}
Fauna 拥有一个针对其数据库的 GraphQL 端点,它会为模式中定义的数据类型生成必要的 mutation(如 create、update、delete)。例如,Fauna 会为数据类型 Quote
生成一个名为 createQuote
的 mutation,用于在数据库中创建新的语录。我们还额外定义了一个名为 allQuotes
的查询字段,它返回数据库中的所有语录。
接下来编写代码,以便 Deno Deploy 应用程序与 Fauna 交互。
要与 Fauna 交互,我们需要向其 GraphQL 端点发送 POST 请求,并附带适当的查询和参数以获取返回数据。因此,让我们构建一个通用函数来处理这些事情。
async function queryFauna(
query: string,
variables: { [key: string]: unknown },
): Promise<{
data?: any;
error?: any;
}> {
// Grab the secret from the environment.
const token = Deno.env.get("FAUNA_SECRET");
if (!token) {
throw new Error("environment variable FAUNA_SECRET not set");
}
try {
// Make a POST request to fauna's graphql endpoint with body being
// the query and its variables.
const res = await fetch("https://graphql.fauna.com/graphql", {
method: "POST",
headers: {
authorization: `Bearer ${token}`,
"content-type": "application/json",
},
body: JSON.stringify({
query,
variables,
}),
});
const { data, errors } = await res.json();
if (errors) {
// Return the first error if there are any.
return { data, error: errors[0] };
}
return { data };
} catch (error) {
return { error };
}
}
将此代码添加到 quotes.ts
文件中。现在,让我们继续更新端点以使用 Fauna。
async function handleQuotes(request: Request) {
const { error, body } = await validateRequest(request, {
GET: {},
POST: {
body: ["quote", "author"],
},
});
if (error) {
return json({ error: error.message }, { status: error.status });
}
if (request.method === "POST") {
+ const { quote, author, error } = await createQuote(
+ body as { quote: string; author: string }
+ );
+ if (error) {
+ return json({ error: "couldn't create the quote" }, { status: 500 });
+ }
return json({ quote, author }, { status: 201 });
}
return json({ quotes });
}
+async function createQuote({
+ quote,
+ author,
+}: {
+ quote: string;
+ author: string;
+}): Promise<{ quote?: string; author?: string; error?: string }> {
+ const query = `
+ mutation($quote: String!, $author: String!) {
+ createQuote(data: { quote: $quote, author: $author }) {
+ quote
+ author
+ }
+ }
+ `;
+
+ const { data, error } = await queryFauna(query, { quote, author });
+ if (error) {
+ return { error };
+ }
+
+ return data;
+}
既然我们已经更新了代码以插入新的语录,在测试代码之前,让我们先设置一个 Fauna 数据库。
创建一个新数据库
- 访问 https://dashboard.fauna.com(如果需要,请登录)并点击 New Database(新建数据库)
- 填写 Database Name(数据库名称)字段,然后点击 Save(保存)。
- 点击左侧边栏可见的 GraphQL 部分。
- 创建一个以
.gql
扩展名结尾的文件,内容为我们上面定义的模式。
生成一个用于访问数据库的 secret
- 点击 Security(安全)部分,然后点击 New Key(新建密钥)。
- 选择 Server(服务器)角色,然后点击 Save(保存)。复制 secret。
现在我们使用 secret 运行应用程序。
FAUNA_SECRET=<the_secret_you_just_obtained> deno run --allow-net=:8000 --watch quotes.ts
# Listening on http://0.0.0.0:8000
curl --dump-header - --request POST --data '{"quote": "A program that has not been tested does not work.", "author": "Bjarne Stroustrup"}' http://127.0.0.1:8000/quotes
请注意语录是如何添加到您的 FaunaDB 集合中的。
让我们编写一个新函数来获取所有语录。
async function getAllQuotes() {
const query = `
query {
allQuotes {
data {
quote
author
}
}
}
`;
const {
data: {
allQuotes: { data: quotes },
},
error,
} = await queryFauna(query, {});
if (error) {
return { error };
}
return { quotes };
}
并使用以下代码更新 handleQuotes
函数。
-// To get started, let's just use a global array of quotes.
-const quotes = [
- {
- quote: "Those who can imagine anything, can create the impossible.",
- author: "Alan Turing",
- },
- {
- quote: "Any sufficiently advanced technology is equivalent to magic.",
- author: "Arthur C. Clarke",
- },
-];
async function handleQuotes(request: Request) {
const { error, body } = await validateRequest(request, {
GET: {},
POST: {
body: ["quote", "author"],
},
});
if (error) {
return json({ error: error.message }, { status: error.status });
}
if (request.method === "POST") {
const { quote, author, error } = await createQuote(
body as { quote: string; author: string },
);
if (error) {
return json({ error: "couldn't create the quote" }, { status: 500 });
}
return json({ quote, author }, { status: 201 });
}
+ // It's assumed that the request method is "GET".
+ {
+ const { quotes, error } = await getAllQuotes();
+ if (error) {
+ return json({ error: "couldn't fetch the quotes" }, { status: 500 });
+ }
+
+ return json({ quotes });
+ }
}
curl http://127.0.0.1:8000/quotes
您应该会看到我们插入到数据库中的所有语录。API 的最终代码可在 https://deno.org.cn/examples/fauna.ts 获取。
部署 API 跳转到标题
现在万事俱备,让我们部署您的新 API!
- 在浏览器中,访问 Deno Deploy 并关联您的 GitHub 账户。
- 选择包含您新 API 的仓库。
- 您可以为项目命名,或允许 Deno 为您生成一个
- 在 Entrypoint 下拉菜单中选择
index.ts
- 点击“部署项目”
为了让您的应用程序正常工作,我们需要配置其环境变量。
在您项目的成功页面或项目仪表板中,点击 Add environmental variables(添加环境变量)。在 Environment Variables(环境变量)下,点击 + Add Variable(+ 添加变量)。创建一个名为 FAUNA_SECRET
的新变量——其值应为我们之前创建的 secret。
点击保存变量。
在您的项目概览中,点击 View(查看)以在浏览器中查看项目,在 URL 末尾添加 /quotes
以查看 FaunaDB 的内容。