Bước 1: Tạo namespace KV Step 1: Create a KV Namespace ជំហាន 1: បង្កើត namespace KV
- Chúng ta đang xây What we're building អ្វីដែលយើងកំពុងសង់
- A KV namespace called BOOKMARKS bound to your Worker.
- Vì sao quan trọng Why this matters ហេតុអ្វីសំខាន់
- KV provides globally distributed, persistent key-value storage that survives Worker restarts and deployments.
npx wrangler kv namespace create "BOOKMARKS" When prompted “Would you like Wrangler to add it on your behalf?”, type Y. This adds the KV binding to your wrangler.jsonc.
When prompted “Would you like Wrangler to add it on your behalf?”, type Y. This adds the KV binding to your wrangler.jsonc.
When prompted “Would you like Wrangler to add it on your behalf?”, type Y. This adds the KV binding to your wrangler.jsonc.
Then regenerate your types so TypeScript knows about the binding:
Then regenerate your types so TypeScript knows about the binding:
Then regenerate your types so TypeScript knows about the binding:
npx wrangler types Bước 2: Đổi handler sang KV Step 2: Update the Handlers to Use KV ជំហាន 2: ប្តូរ handler ទៅ KV
- Chúng ta đang xây What we're building អ្វីដែលយើងកំពុងសង់
- All four CRUD handlers using KV instead of the in-memory Map.
- Vì sao quan trọng Why this matters ហេតុអ្វីសំខាន់
- This is the core migration. The route structure stays the same; only the data access layer changes.
Replace the entire src/index.ts with the following. The key changes from Step 2 are marked with comments:
Replace the entire src/index.ts with the following. The key changes from Step 2 are marked with comments:
Replace the entire src/index.ts with the following. The key changes from Step 2 are marked with comments:
interface Bookmark {
id: string;
url: string;
title: string;
createdAt: string;
}
// REMOVED: const bookmarks: Map<string, Bookmark> = new Map();
// Bookmarks are now stored in KV via env.BOOKMARKS
export default {
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
const url = new URL(request.url);
const path = url.pathname;
const method = request.method;
// CHANGED: all handlers now receive env for KV access
if (path === '/bookmarks' && method === 'GET') {
return listBookmarks(env);
}
if (path === '/bookmarks' && method === 'POST') {
return createBookmark(request, env);
}
const match = path.match(/^\/bookmarks\/([a-zA-Z0-9_-]+)$/);
if (match && method === 'GET') {
return getBookmark(match[1], env);
}
if (match && method === 'DELETE') {
return deleteBookmark(match[1], env);
}
if (path === '/') {
return Response.json({
name: 'Bookmark API',
version: '2.0.0',
storage: 'Workers KV',
endpoints: ['GET /bookmarks', 'POST /bookmarks', 'GET /bookmarks/:id', 'DELETE /bookmarks/:id']
});
}
return Response.json({ error: 'Not Found' }, { status: 404 });
},
};
// CHANGED: list from KV using .list() + individual .get() calls
// NOTE: This fetches each value individually (N+1 pattern). Fine for small
// datasets, but for better performance at scale, store titles as KV metadata
// so you can list without fetching each value (see the challenge below).
async function listBookmarks(env: Env): Promise<Response> {
const keys = await env.BOOKMARKS.list();
const all: Bookmark[] = [];
for (const key of keys.keys) {
const value = await env.BOOKMARKS.get<Bookmark>(key.name, 'json');
if (value) all.push(value);
}
return Response.json({ bookmarks: all, count: all.length });
}
// CHANGED: store in KV with .put()
async function createBookmark(request: Request, env: Env): Promise<Response> {
let body: { url?: string; title?: string };
try {
body = await request.json() as { url?: string; title?: string };
} catch {
return Response.json(
{ error: 'Invalid JSON in request body' },
{ status: 400 }
);
}
if (!body.url || !body.title) {
return Response.json(
{ error: 'Missing required fields: url, title' },
{ status: 400 }
);
}
const id = crypto.randomUUID().slice(0, 8);
const bookmark: Bookmark = {
id,
url: body.url,
title: body.title,
createdAt: new Date().toISOString(),
};
// Store as JSON in KV, keyed by ID
await env.BOOKMARKS.put(id, JSON.stringify(bookmark));
return Response.json(bookmark, { status: 201 });
}
// CHANGED: retrieve from KV with .get()
async function getBookmark(id: string, env: Env): Promise<Response> {
const bookmark = await env.BOOKMARKS.get<Bookmark>(id, 'json');
if (!bookmark) {
return Response.json({ error: 'Bookmark not found' }, { status: 404 });
}
return Response.json(bookmark);
}
// CHANGED: delete from KV with .delete()
async function deleteBookmark(id: string, env: Env): Promise<Response> {
const existing = await env.BOOKMARKS.get(id);
if (!existing) {
return Response.json({ error: 'Bookmark not found' }, { status: 404 });
}
await env.BOOKMARKS.delete(id);
return Response.json({ message: 'Bookmark deleted' });
} Bước 3: Test lưu bền Step 3: Test Persistent Storage ជំហាន 3: Test ការរក្សាទុកជាប់
- Chúng ta đang xây What we're building អ្វីដែលយើងកំពុងសង់
- Bookmarks that survive a Worker restart.
- Vì sao quan trọng Why this matters ហេតុអ្វីសំខាន់
- This confirms KV is working. The same tests from Step 2 should pass, but now data persists.
Tạo và kiểm tra persistence Create and verify persistence បង្កើត និងពិនិត្យ persistence
Create a bookmark:
Create a bookmark:
Create a bookmark:
curl -X POST http://localhost:8787/bookmarks \
-H "Content-Type: application/json" \
-d '{"url":"https://developers.cloudflare.com/kv/","title":"KV Docs"}' List bookmarks:
List bookmarks:
List bookmarks:
curl -s http://localhost:8787/bookmarks | jq Now stop the dev server (Ctrl+C) and restart it (npm run dev). Then list again:
Now stop the dev server (Ctrl+C) and restart it (npm run dev). Then list again:
Now stop the dev server (Ctrl+C) and restart it (npm run dev). Then list again:
curl -s http://localhost:8787/bookmarks | jq If the bookmark is still there, KV is working.
If the bookmark is still there, KV is working.
If the bookmark is still there, KV is working.
Test mọi thao tác Test all operations Test ប្រតិបត្តិការទាំងអស់
Create a bookmark:
Create a bookmark:
Create a bookmark:
curl -X POST http://localhost:8787/bookmarks \
-H "Content-Type: application/json" \
-d '{"url":"https://developers.cloudflare.com/workers-ai/","title":"Workers AI Docs"}' Get by ID (use the id from the create response):
Get by ID (use the id from the create response):
Get by ID (use the id from the create response):
curl -s http://localhost:8787/bookmarks/REPLACE_ID | jq Delete:
Delete:
Delete:
curl -X DELETE http://localhost:8787/bookmarks/REPLACE_ID Verify deletion (should return 404):
Verify deletion (should return 404):
Verify deletion (should return 404):
curl http://localhost:8787/bookmarks/REPLACE_ID