在本教程中,我們將學(xué)習(xí)如何使用 D1 來(lái)搭建一個(gè)無(wú)服務(wù)器評(píng)論服務(wù)。為此,我們將構(gòu)建一個(gè)新的 D1 數(shù)據(jù)庫(kù),并構(gòu)建一個(gè)允許創(chuàng)建和檢索評(píng)論的 JSON API。這可能是國(guó)內(nèi)第一篇詳細(xì)介紹 D1 和具體寫法的博文了(bushi)
設(shè)置你的項(xiàng)目
在此示例中,我們將使用 Hono,一個(gè) Express.js 風(fēng)格的框架,用于構(gòu)建我們的 API。要在此項(xiàng)目中使用 Hono,請(qǐng)使用 npm 安裝它:
npm install hono
接下來(lái),在 src/index.ts 中,初始化一個(gè)新的 Hono 應(yīng)用程序:
import { Hono } from "hono";
const app = new Hono();
app.get("/posts/:slug/comments", async (c) => {
// Do something and return an HTTP response
// Optionally, do something with `c.req.param("slug")`
});
app.post("/posts/:slug/comments", async (c) => {
// Do something and return an HTTP response
// Optionally, do something with `c.req.param("slug")`
});
export default app;
創(chuàng)建數(shù)據(jù)庫(kù)
我們現(xiàn)在將創(chuàng)建一個(gè) D1 數(shù)據(jù)庫(kù)。在 Wrangler 2 中,支持 d1 子命令,它允許我們直接從命令行創(chuàng)建和查詢 D1 數(shù)據(jù)庫(kù)。使用以下命令創(chuàng)建一個(gè)新數(shù)據(jù)庫(kù):
wrangler d1 create d1-example
通過(guò)在我們的文件 Wrangler 的配置文件中創(chuàng)建綁定,在我們的 Worker 代碼中引用我們創(chuàng)建的數(shù)據(jù)庫(kù)。wrangler.toml 綁定允許我們?cè)诖a中使用一個(gè)簡(jiǎn)單的變量名來(lái)訪問(wèn) Cloudflare 資源,例如 D1 數(shù)據(jù)庫(kù)、KV 命名空間和 R2 存儲(chǔ)桶。在wrangler.toml中,設(shè)置綁定 DB 并將其連接到database_name和database_id:
[[ d1_databases ]]
binding = "DB" # available in your Worker on `env.DB`
database_name = "d1-example"
database_id = "4e1c28a9-90e4-41da-8b4b-6cf36e5abb29"
通過(guò)在文件中配置綁定 wrangler.toml,我們可以從命令行和 Workers 函數(shù)內(nèi)部與數(shù)據(jù)庫(kù)進(jìn)行交互。
與 D1 互動(dòng)
通過(guò)使用以下命令發(fā)出直接 SQL 命令與 D1 交互 wrangler d1 execute:
wrangler d1 execute d1-example --command "SELECT name FROM sqlite_schema WHERE type ='table'"
我們還可以傳遞一個(gè) SQL 文件 - 非常適合在單個(gè)命令中進(jìn)行初始數(shù)據(jù)格式化。創(chuàng)建 schemas/schema.sql,這將為我們的項(xiàng)目創(chuàng)建一個(gè)新 comments 表:
DROP TABLE IF EXISTS comments;
CREATE TABLE IF NOT EXISTS comments (
id integer PRIMARY KEY AUTOINCREMENT,
author text NOT NULL,
body text NOT NULL,
pathname text NOT NULL
);
CREATE INDEX idx_comments_pathname ON comments (pathname);
-- Optionally, use the below query to create data
INSERT INTO COMMENTS (author, body, pathname) VALUES ("甜力怕", "Great post!", "/hello-world.thml");
創(chuàng)建文件后,通過(guò)將標(biāo)志傳遞給 D1 數(shù)據(jù)庫(kù)來(lái)執(zhí)行模式文件--file:
wrangler d1 execute d1-example --file schemas/schema.sql
執(zhí)行 SQL
在前面的步驟中,我們創(chuàng)建了一個(gè) SQL 數(shù)據(jù)庫(kù)并用初始數(shù)據(jù)填充了它。現(xiàn)在,我們將向我們的 Workers 函數(shù)添加一個(gè)路由,以從該數(shù)據(jù)庫(kù)檢索數(shù)據(jù)。根據(jù) wrangler.toml 前面步驟中的配置,現(xiàn)在可以通過(guò) DB 綁定訪問(wèn) D1 數(shù)據(jù)庫(kù)。在我們的代碼中,使用綁定來(lái)準(zhǔn)備 SQL 語(yǔ)句并執(zhí)行它們,例如,檢索注釋:
app.get("/posts/:slug/comments", async (c) => {
const { slug } = c.req.param();
const { results } = await c.env.DB.prepare(
`
select * from comments where pathname = ?
`
)
.bind(slug)
.all();
return c.json(results);
});
上面的代碼使用 D1 綁定上的函數(shù)來(lái)準(zhǔn)備和執(zhí)行 SQL 語(yǔ)句。
在此函數(shù)中,我們接受一個(gè) URL 查詢參數(shù)id并設(shè)置一個(gè)新的 SQL 語(yǔ)句,我們可以在其中選擇所有與我們的查詢參數(shù)id具有匹配值的評(píng)論。然后,我們可以將其作為簡(jiǎn)單的 JSON 響應(yīng)返回。
插入數(shù)據(jù)
通過(guò)完成上一步,您已經(jīng)建立了對(duì)數(shù)據(jù)的只讀訪問(wèn)權(quán)限。接下來(lái),您將在src/index.ts中定義另一個(gè)端點(diǎn)函數(shù),該函數(shù)允許通過(guò)將數(shù)據(jù)插入數(shù)據(jù)庫(kù)來(lái)創(chuàng)建新評(píng)論:
app.post("/posts/:slug/comments", async (c) => {
const { slug } = c.req.param();
const { author, body } = await c.req.json();
if (!author) return c.text("Missing author value for new comment");
if (!body) return c.text("Missing body value for new comment");
const { success } = await c.env.DB.prepare(
`
insert into comments (author, body, pathname) values (?, ?, ?)
`
)
.bind(author, body, slug)
.run();
if (success) {
c.status(201);
return c.text("Created");
} else {
c.status(500);
return c.text("Something went wrong");
}
});
部署
在您的應(yīng)用程序準(zhǔn)備好部署后,使用 Wrangler 構(gòu)建您的項(xiàng)目并將其發(fā)布到 Cloudflare 網(wǎng)絡(luò)。
首先運(yùn)行 wrangler whoami 以確認(rèn)您已登錄到您的 Cloudflare 帳戶。如果您未登錄,Wrangler 將提示您登錄,創(chuàng)建一個(gè) API 密鑰,您可以使用該密鑰從本地計(jì)算機(jī)自動(dòng)發(fā)出經(jīng)過(guò)身份驗(yàn)證的請(qǐng)求。
登錄后,確認(rèn)您的wrangler.toml文件的配置與下圖類似。您可以將name字段更改為您選擇的項(xiàng)目名稱:
name = "d1-example"
main = "src/index.ts"
compatibility_date = "2023-01-15"
[[ d1_databases ]]
binding = "DB" # available in your Worker on env.DB
database_name = "<YOUR_DATABASE_NAME>"
database_id = "<YOUR_DATABASE_UUID>"
現(xiàn)在,運(yùn)行wrangler publish將您的項(xiàng)目發(fā)布到 Cloudflare。成功發(fā)布后,通過(guò)發(fā)出GET請(qǐng)求以檢索相關(guān)帖子的評(píng)論來(lái)測(cè)試 API。由于您還沒(méi)有任何帖子,此響應(yīng)將為空,但無(wú)論如何它仍會(huì)向 D1 數(shù)據(jù)庫(kù)發(fā)出請(qǐng)求,您可以使用它來(lái)確認(rèn)應(yīng)用程序已正確部署:
# Note: Your workers.dev deployment URL may be different
$ curl https://d1-example.helloworld.workers.dev/posts/hello-world/comments
[
{
"id": 1,
"author": "甜力怕",
"body": "Hello from the comments section!",
"pathname": "/hello-world.html"
}
]
使用前端進(jìn)行測(cè)試
此應(yīng)用程序只是一個(gè) API 后端,最好與用于創(chuàng)建和查看評(píng)論的前端 UI 一起使用。要使用預(yù)構(gòu)建的前端 UI 測(cè)試此后端,請(qǐng)參閱文末。值得注意的是,loadComments 和 submitComment 函數(shù)向該站點(diǎn)的部署版本發(fā)出請(qǐng)求,這意味著您可以使用前端并將 URL 替換為本教程中代碼庫(kù)的部署版本,以使用您自己的數(shù)據(jù)。
請(qǐng)注意,從前端與此 API 交互需要在后端 API 中啟用特定的跨源資源共享(或CORS)標(biāo)頭。幸運(yùn)的是,Hono 有一種快速的方法可以為您的應(yīng)用程序啟用此功能。導(dǎo)入cors模塊并將其作為中間件添加到src/index.ts中的 API :
import { Hono } from "hono";
import { cors } from "hono/cors";
const app = new Hono();
app.use("/*", cors());現(xiàn)在,當(dāng)您向/*發(fā)出請(qǐng)求時(shí),Hono 將自動(dòng)生成 CORS 標(biāo)頭并將其添加到來(lái)自您的 API 的響應(yīng)中,從而允許前端 UI 與其交互而不會(huì)出錯(cuò)。
文末福利 - 前端代碼
<template>
<div class="post">
<h1 v-text="post.title" />
<p v-text="post.content" />
<h3>Comments (<span v-text="post.comments ? post.comments.length : 0" />)</h3>
<form v-on:submit="submitComment">
<textarea
required
placeholder="寫下你的留言"
v-model="comment.body"
cols="40"
rows="4"
/>
<input required type="text" placeholder="您的名稱" v-model="comment.author" />
<input type="submit" />
</form>
<span v-if="!post.comments && loadingComments">加載評(píng)論中...</span>
<div v-if="post.comments">
<div v-for="comment in post.comments">
<p v-text="sanitize(comment.body)"></p>
<p>
<em>- {{ sanitize(comment.author) }}</em>
</p>
</div>
</div>
</div>
</template>
<script type="module">
const posts = {
'hello-world': {
title: 'Hello World!',
content: 'Testing, one two',
slug: '/hello-world.html',
},
};
export default {
data() {
return {
comment: {
author: '',
body: '',
},
post: null,
loadingComments: false,
};
},
mounted() {
const param = this.$route.params.post;
if (posts[param]) {
this.post = posts[param];
this.loadComments();
} else {
throw new Error("無(wú)法找到博文");
}
},
methods: {
async loadComments() {
this.loadingComments = true;
const resp = await fetch(
`https://d1-example.helloworld.workers.dev/posts/${this.post.slug}/comments`
);
const comments = await resp.json();
this.post.comments = comments;
this.loadingComments = false;
},
async submitComment(evt) {
evt.preventDefault();
const newComment = {
body: this.sanitize(this.comment.body),
author: this.sanitize(this.comment.author),
};
const resp = await fetch(
`https://d1-example.helloworld.workers.dev/posts/${this.post.slug}/comments`,
{
method: 'POST',
body: JSON.stringify(newComment),
}
);
if (resp.status == 201) this.post.comments.push(newComment);
this.comment.author = '';
this.comment.body = '';
},
sanitize(str) {
/**
* 1. g全局匹配,找到所有匹配,而不是在第一個(gè)匹配后停止
* 2. i匹配全部大小寫
* 3. m多行,將開始和結(jié)束字符(^和$)視為在多行上工作,而不只是只匹配整個(gè)輸入字符串的最開
* 始和最末尾處。
* 4. s與m相反,單行匹配
*/
str = str.replace(/[^a-z0-9 \.,_-]/gim, '');
return str.trim();
},
},
};
</script>
作者:HackPig520
出處:http://www.rzrgm.cn/xiaozhu2020/p/d1-demo.html
本文版權(quán)歸作者所有,歡迎轉(zhuǎn)載,但未經(jīng)作者同意必須保留此段聲明,且在文章頁(yè)面明顯位置給出原文連接,否則保留追究法律責(zé)任的權(quán)利。
浙公網(wǎng)安備 33010602011771號(hào)