deno.com
本頁面內容

API 伺服器與 FaunaDB

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 請求遵循提供的 body 結構。

-  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 端點,並且它為結構描述中定義的資料類型產生必要的變更,例如 create、update、delete。例如,fauna 將產生一個名為 createQuote 的變更,以在資料庫中為資料類型 Quote 建立新的語錄。我們還額外定義了一個名為 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 副檔名結尾的檔案,內容為我們上面定義的結構描述。

產生用於存取資料庫的密鑰

  1. 點擊 Security(安全性)區段,然後點擊 New Key(新增金鑰)。
  2. 選取 Server(伺服器)角色,然後點擊 Save(儲存)。複製密鑰。

現在讓我們使用密鑰執行應用程式。

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.com/examples/fauna.ts 取得。

部署 API 跳至標題

現在我們已準備就緒,讓我們部署您的新 API!

  1. 在您的瀏覽器中,造訪 Deno Deploy 並連結您的 GitHub 帳戶。
  2. 選取包含您新 API 的儲存庫。
  3. 您可以為您的專案命名,或允許 Deno 為您產生一個名稱
  4. 在 Entrypoint(進入點)下拉式選單中選取 index.ts
  5. 點擊 Deploy Project(部署專案)

為了使您的應用程式運作,我們需要設定其環境變數。

在您專案的成功頁面或專案儀表板中,點擊 Add environmental variables(新增環境變數)。在 Environment Variables(環境變數)下,點擊 + Add Variable(+ 新增變數)。建立一個名為 FAUNA_SECRET 的新變數 - 值應為我們稍早建立的密鑰。

點擊以儲存變數。

在您的專案概觀中,點擊 View(檢視)以在您的瀏覽器中檢視專案,在網址末尾新增 /quotes 以查看 FaunaDB 的內容。

您找到需要的資訊了嗎?

隱私權政策