deno.com
本页内容

带 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 数据库。

创建一个新数据库

  1. 访问 https://dashboard.fauna.com(如果需要,请登录)并点击 New Database(新建数据库)
  2. 填写 Database Name(数据库名称)字段,然后点击 Save(保存)。
  3. 点击左侧边栏可见的 GraphQL 部分。
  4. 创建一个以 .gql 扩展名结尾的文件,内容为我们上面定义的模式。

生成一个用于访问数据库的 secret

  1. 点击 Security(安全)部分,然后点击 New Key(新建密钥)。
  2. 选择 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!

  1. 在浏览器中,访问 Deno Deploy 并关联您的 GitHub 账户。
  2. 选择包含您新 API 的仓库。
  3. 您可以为项目命名,或允许 Deno 为您生成一个
  4. 在 Entrypoint 下拉菜单中选择 index.ts
  5. 点击“部署项目

为了让您的应用程序正常工作,我们需要配置其环境变量。

在您项目的成功页面或项目仪表板中,点击 Add environmental variables(添加环境变量)。在 Environment Variables(环境变量)下,点击 + Add Variable(+ 添加变量)。创建一个名为 FAUNA_SECRET 的新变量——其值应为我们之前创建的 secret。

点击保存变量。

在您的项目概览中,点击 View(查看)以在浏览器中查看项目,在 URL 末尾添加 /quotes 以查看 FaunaDB 的内容。

您找到所需内容了吗?

隐私政策