deno.com
本頁面內容

API 伺服器搭配 Postgres

Postgres 因為其彈性與易用性,是熱門的網頁應用程式資料庫。本指南將示範如何搭配 Deno Deploy 使用 Postgres。

總覽 Jump to heading

我們將為一個簡易的待辦事項應用程式建立 API。它將有兩個端點

GET /todos 將回傳所有待辦事項列表,而 POST /todos 將建立一個新的待辦事項。

GET /todos
---
title: "returns a list of all todos"
---
[
  {
    "id": 1,
    "title": "Buy bread"
  },
  {
    "id": 2,
    "title": "Buy rice"
  },
  {
    "id": 3,
    "title": "Buy spices"
  }
]

POST /todos
---
title: "creates a new todo"
---
"Buy milk"
---
title: "returns a 201 status code"
---

在本教學中,我們將會

設定 Postgres Jump to heading

本教學將完全著重於連線到未加密的 Postgres。如果您想要使用加密與自訂 CA 憑證,請使用此處的文件。

為了開始,我們需要建立一個新的 Postgres 執行個體供我們連線。在本教學中,您可以使用 Neon PostgresSupabase,因為它們都提供免費、託管的 Postgres 執行個體。如果您想將資料庫託管在其他地方,您也可以這麼做。

Neon Postgres Jump to heading

  1. 造訪 https://neon.tech/ 並點擊 Sign up 以使用電子郵件、Github、Google 或合作夥伴帳戶註冊。註冊後,您會被導向 Neon Console 以建立您的第一個專案。

  2. 輸入您的專案名稱,選擇 Postgres 版本,提供資料庫名稱,並選擇區域。一般來說,您會想要選擇最靠近您應用程式的區域。完成後,點擊 Create project

  3. 您會看到新專案的連線字串,您可以使用它來連線到您的資料庫。儲存連線字串,它看起來像這樣

    postgres://alex:AbC123dEf@ep-cool-darkness-123456.us-east-2.aws.neon.tech/dbname?sslmode=require
    

Supabase Jump to heading

  1. 造訪 https://app.supabase.io/ 並點擊「New project」。
  2. 為您的資料庫選擇名稱、密碼和區域。請務必儲存密碼,因為您稍後會需要它。
  3. 點擊「Create new project」。建立專案可能需要一段時間,請耐心等候。
  4. 專案建立完成後,導覽至左側的「Database」標籤。
  5. 前往「Connection Pooling」設定,並從「Connection String」欄位複製連線字串。這是您將用來連線到資料庫的連線字串。將您稍早儲存的密碼插入此字串中,然後將字串儲存在某處 - 您稍後會需要它。

撰寫與部署應用程式 Jump to heading

我們現在可以開始撰寫我們的應用程式。首先,我們將在控制面板中建立一個新的 Deno Deploy playground:按下 https://dash.deno.com/projects 上的「New Playground」按鈕。

這將開啟 playground 編輯器。在我們真正開始撰寫程式碼之前,我們需要將 Postgres 連線字串放入環境變數中。若要執行此操作,請點擊編輯器左上角的專案名稱。這將開啟專案設定。

從這裡,您可以透過左側導覽選單導覽至「Settings」->「Environment Variable」標籤。在「Key」欄位中輸入「DATABASE_URL」,並將您的連線字串貼到「Value」欄位中。現在,按下「Add」。您的環境變數現在已設定。

讓我們回到編輯器:若要執行此操作,請透過左側導覽選單前往「Overview」標籤,然後按下「Open Playground」。讓我們從使用 Deno.serve() 服務 HTTP 請求開始

Deno.serve(async (req) => {
  return new Response("Not Found", { status: 404 });
});

您已經可以使用 Ctrl+S (或 Mac 上的 Cmd+S) 儲存此程式碼。您應該會在右側看到預覽頁面自動重新整理:現在顯示「Not Found」。

接下來,讓我們匯入 Postgres 模組,從環境變數讀取連線字串,並建立連線池。

import * as postgres from "https://deno.land/x/postgres@v0.14.0/mod.ts";

// Get the connection string from the environment variable "DATABASE_URL"
const databaseUrl = Deno.env.get("DATABASE_URL")!;

// Create a database pool with three connections that are lazily established
const pool = new postgres.Pool(databaseUrl, 3, true);

同樣地,您現在可以儲存此程式碼,但這次您應該看不到任何變更。我們正在建立連線池,但我們實際上尚未對資料庫執行任何查詢。在我們可以執行此操作之前,我們需要設定我們的資料表結構描述。

我們想要儲存待辦事項列表。讓我們建立一個名為 todos 的資料表,其中包含一個自動遞增的 id 欄位和一個 title 欄位

const pool = new postgres.Pool(databaseUrl, 3, true);

// Connect to the database
const connection = await pool.connect();
try {
  // Create the table
  await connection.queryObject`
    CREATE TABLE IF NOT EXISTS todos (
      id SERIAL PRIMARY KEY,
      title TEXT NOT NULL
    )
  `;
} finally {
  // Release the connection back into the pool
  connection.release();
}

現在我們有一個資料表,我們可以為 GET 和 POST 端點新增 HTTP 處理常式。

Deno.serve(async (req) => {
  // Parse the URL and check that the requested endpoint is /todos. If it is
  // not, return a 404 response.
  const url = new URL(req.url);
  if (url.pathname !== "/todos") {
    return new Response("Not Found", { status: 404 });
  }

  // Grab a connection from the database pool
  const connection = await pool.connect();

  try {
    switch (req.method) {
      case "GET": { // This is a GET request. Return a list of all todos.
        // Run the query
        const result = await connection.queryObject`
          SELECT * FROM todos
        `;

        // Encode the result as JSON
        const body = JSON.stringify(result.rows, null, 2);

        // Return the result as JSON
        return new Response(body, {
          headers: { "content-type": "application/json" },
        });
      }
      case "POST": { // This is a POST request. Create a new todo.
        // Parse the request body as JSON. If the request body fails to parse,
        // is not a string, or is longer than 256 chars, return a 400 response.
        const title = await req.json().catch(() => null);
        if (typeof title !== "string" || title.length > 256) {
          return new Response("Bad Request", { status: 400 });
        }

        // Insert the new todo into the database
        await connection.queryObject`
          INSERT INTO todos (title) VALUES (${title})
        `;

        // Return a 201 Created response
        return new Response("", { status: 201 });
      }
      default: // If this is neither a POST, or a GET return a 405 response.
        return new Response("Method Not Allowed", { status: 405 });
    }
  } catch (err) {
    console.error(err);
    // If an error occurs, return a 500 response
    return new Response(`Internal Server Error\n\n${err.message}`, {
      status: 500,
    });
  } finally {
    // Release the connection back into the pool
    connection.release();
  }
});

這樣就完成了 - 應用程式完成。透過儲存編輯器來部署此程式碼。您現在可以 POST 到 /todos 端點以建立新的待辦事項,並且可以透過對 /todos 發出 GET 請求來取得所有待辦事項的列表

$ curl -X GET https://tutorial-postgres.deno.dev/todos
[]⏎

$ curl -X POST -d '"Buy milk"' https://tutorial-postgres.deno.dev/todos

$ curl -X GET https://tutorial-postgres.deno.dev/todos
[
  {
    "id": 1,
    "title": "Buy milk"
  }
]

一切都正常運作 🎉

本教學的完整程式碼

作為額外挑戰,嘗試新增 DELETE /todos/:id 端點以刪除待辦事項。URLPattern API 可以協助您完成此操作。

您找到需要的資訊了嗎?

隱私權政策