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"
---
在本教學中,我們將會
- 在 Neon Postgres 或 Supabase 上建立與設定 Postgres 執行個體。
- 使用 Deno Deploy Playground 來開發與部署應用程式。
- 使用 cURL 測試我們的應用程式。
設定 Postgres Jump to heading
本教學將完全著重於連線到未加密的 Postgres。如果您想要使用加密與自訂 CA 憑證,請使用此處的文件。
為了開始,我們需要建立一個新的 Postgres 執行個體供我們連線。在本教學中,您可以使用 Neon Postgres 或 Supabase,因為它們都提供免費、託管的 Postgres 執行個體。如果您想將資料庫託管在其他地方,您也可以這麼做。
Neon Postgres Jump to heading
-
造訪 https://neon.tech/ 並點擊 Sign up 以使用電子郵件、Github、Google 或合作夥伴帳戶註冊。註冊後,您會被導向 Neon Console 以建立您的第一個專案。
-
輸入您的專案名稱,選擇 Postgres 版本,提供資料庫名稱,並選擇區域。一般來說,您會想要選擇最靠近您應用程式的區域。完成後,點擊 Create project。
-
您會看到新專案的連線字串,您可以使用它來連線到您的資料庫。儲存連線字串,它看起來像這樣
postgres://alex:AbC123dEf@ep-cool-darkness-123456.us-east-2.aws.neon.tech/dbname?sslmode=require
Supabase Jump to heading
- 造訪 https://app.supabase.io/ 並點擊「New project」。
- 為您的資料庫選擇名稱、密碼和區域。請務必儲存密碼,因為您稍後會需要它。
- 點擊「Create new project」。建立專案可能需要一段時間,請耐心等候。
- 專案建立完成後,導覽至左側的「Database」標籤。
- 前往「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 可以協助您完成此操作。