Cloudflare Workers Cloudflare Workers Cloudflare Workers · Lab 2/7 Lab 2/7 Lab 2/7 · 20 phút · 20 min · 20 នាទី

02

Thêm route và CRUD Add Routes and CRUD Endpoints បន្ថែម route និង CRUD

Thêm HTTP routing cho Bookmark API: tạo, liệt kê, lấy và xóa bookmark trên store trong bộ nhớ. Add HTTP routing to your Bookmark API with endpoints for creating, listing, retrieving, and deleting bookmarks using an in-memory store. បន្ថែម HTTP routing សម្រាប់ Bookmark API៖ បង្កើត រាយ យក និងលុប bookmark លើ store ក្នុងអង្គចងចាំ។

Nội dung bước (lệnh, code) giữ nguyên tiếng Anh từ nguồn chính thức. Step body (commands, code) stays in English from the official source. ខ្លឹមសារជំហាន (ពាក្យបញ្ជា និង code) រក្សាភាសាអង់គ្លេសពីប្រភពផ្លូវការ។ labs.cloudflare.dev ↗

Cần trước Prerequisites តម្រូវការជាមុន

  • Đã xong bước 1 Completed Step 1 បានបញ្ចប់ជំហាន 1
  • Project bookmark-api đang chạy local bookmark-api project running locally Project bookmark-api កំពុងរត់ local

Bạn sẽ làm được Learning objectives គោលបំណងសិក្សា

  • Thiết kế routing theo URL và method, không cần framework Design a URL and method-based routing layer without external frameworks រចនា routing តាម URL និង method ដោយមិនប្រើ framework
  • Parse body, validate input, trả lỗi có cấu trúc Parse request bodies, validate input, and return structured error responses Parse body, validate input, ត្រឡប់ error មានរចនាសម្ព័ន្ធ
  • Implement vòng đời CRUD cho một REST resource Implement a full CRUD lifecycle for a REST resource Implement វដ្ត CRUD ពេញសម្រាប់ REST resource

Bước 1: Định nghĩa type Bookmark Step 1: Define the Bookmark Type ជំហាន 1: កំណត់ type Bookmark

Chúng ta đang xây What we're building អ្វីដែលយើងកំពុងសង់
A TypeScript interface that describes a bookmark.
Vì sao quan trọng Why this matters ហេតុអ្វីសំខាន់
Defining types up front makes the code self-documenting and catches mistakes at development time.

Open src/index.ts and replace the entire file with the following. Start by defining the Bookmark interface and an in-memory store:

Open src/index.ts and replace the entire file with the following. Start by defining the Bookmark interface and an in-memory store:

Open src/index.ts and replace the entire file with the following. Start by defining the Bookmark interface and an in-memory store:

typescript

interface Bookmark {
  id: string;
  url: string;
  title: string;
  createdAt: string;
}

// In-memory store (lost on restart, we will fix this in Step 3)
const bookmarks: Map<string, Bookmark> = new Map();

Bước 2: Thêm router Step 2: Add the Router ជំហាន 2: បន្ថែម router

Chúng ta đang xây What we're building អ្វីដែលយើងកំពុងសង់
A fetch handler that routes requests to the correct handler based on path and method.
Vì sao quan trọng Why this matters ហេតុអ្វីសំខាន់
Clean routing is the foundation of any API. Separating route matching from business logic keeps the code maintainable.

Add the fetch handler below the bookmark interface:

Add the fetch handler below the bookmark interface:

Add the fetch handler below the bookmark interface:

typescript
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;

    // GET /bookmarks - list all
    if (path === '/bookmarks' && method === 'GET') {
      return listBookmarks();
    }

    // POST /bookmarks - create
    if (path === '/bookmarks' && method === 'POST') {
      return createBookmark(request);
    }

    // Match /bookmarks/:id pattern (used by both GET and DELETE below)
    const match = path.match(/^\/bookmarks\/([a-zA-Z0-9_-]+)$/);

    // GET /bookmarks/:id - get one
    if (match && method === 'GET') {
      return getBookmark(match[1]);
    }

    // DELETE /bookmarks/:id - delete one
    if (match && method === 'DELETE') {
      return deleteBookmark(match[1]);
    }

    // Fallback
    if (path === '/') {
      return Response.json({
        name: 'Bookmark API',
        endpoints: ['GET /bookmarks', 'POST /bookmarks', 'GET /bookmarks/:id', 'DELETE /bookmarks/:id']
      });
    }

    return Response.json({ error: 'Not Found' }, { status: 404 });
  },
};

Bước 3: Viết handler Step 3: Implement the Handlers ជំហាន 3: សរសេរ handler

Chúng ta đang xây What we're building អ្វីដែលយើងកំពុងសង់
Four handler functions that perform CRUD operations on the in-memory bookmark store.
Vì sao quan trọng Why this matters ហេតុអ្វីសំខាន់
Each handler is a small, focused function. This pattern will carry forward as you swap the storage layer in later steps.

Add these functions below the export default block:

Add these functions below the export default block:

Add these functions below the export default block:

typescript
function listBookmarks(): Response {
  const all = Array.from(bookmarks.values());
  return Response.json({ bookmarks: all, count: all.length });
}

async function createBookmark(request: Request): 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(),
  };

  bookmarks.set(id, bookmark);

  return Response.json(bookmark, { status: 201 });
}

function getBookmark(id: string): Response {
  const bookmark = bookmarks.get(id);
  if (!bookmark) {
    return Response.json({ error: 'Bookmark not found' }, { status: 404 });
  }
  return Response.json(bookmark);
}

function deleteBookmark(id: string): Response {
  if (!bookmarks.has(id)) {
    return Response.json({ error: 'Bookmark not found' }, { status: 404 });
  }
  bookmarks.delete(id);
  return Response.json({ message: 'Bookmark deleted' });
}

Bước 4: Test API Step 4: Test Your API ជំហាន 4: Test API

Chúng ta đang xây What we're building អ្វីដែលយើងកំពុងសង់
All four endpoints tested and working locally.
Vì sao quan trọng Why this matters ហេតុអ្វីសំខាន់
Testing each endpoint confirms the routing, validation, and response format are correct before adding persistence.

Make sure npm run dev is running, then test:

Make sure npm run dev is running, then test:

Make sure npm run dev is running, then test:

Tạo bookmark Create bookmarks បង្កើត bookmark

bash
curl -X POST http://localhost:8787/bookmarks \
  -H "Content-Type: application/json" \
  -d '{"url":"https://developers.cloudflare.com/workers/","title":"Workers Docs"}'
bash
curl -X POST http://localhost:8787/bookmarks \
  -H "Content-Type: application/json" \
  -d '{"url":"https://developers.cloudflare.com/d1/","title":"D1 Docs"}'

Liệt kê bookmark List all bookmarks រាយ bookmark

bash
curl -s http://localhost:8787/bookmarks | jq

Lấy một bookmark Get a single bookmark យក bookmark មួយ

Use an id from the create response:

Use an id from the create response:

Use an id from the create response:

bash
curl -s http://localhost:8787/bookmarks/REPLACE_ID | jq

Xóa bookmark Delete a bookmark លុប bookmark

bash
curl -X DELETE http://localhost:8787/bookmarks/REPLACE_ID

Test trường hợp lỗi Test error cases Test ករណី error

Missing fields — should return 400:

Missing fields — should return 400:

Missing fields — should return 400:

bash
curl -X POST http://localhost:8787/bookmarks \
  -H "Content-Type: application/json" \
  -d '{"url":"https://example.com"}'

Non-existent bookmark — should return 404:

Non-existent bookmark — should return 404:

Non-existent bookmark — should return 404:

bash
curl http://localhost:8787/bookmarks/does-not-exist

File hoàn chỉnh The Complete File File ពេញ

For reference, here is the full src/index.ts after this step:

For reference, here is the full src/index.ts after this step:

For reference, here is the full src/index.ts after this step:

typescript

interface Bookmark {
  id: string;
  url: string;
  title: string;
  createdAt: string;
}

const bookmarks: Map<string, Bookmark> = new Map();

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;

    if (path === '/bookmarks' && method === 'GET') {
      return listBookmarks();
    }

    if (path === '/bookmarks' && method === 'POST') {
      return createBookmark(request);
    }

    // Match /bookmarks/:id pattern (used by both GET and DELETE below)
    const match = path.match(/^\/bookmarks\/([a-zA-Z0-9_-]+)$/);

    if (match && method === 'GET') {
      return getBookmark(match[1]);
    }

    if (match && method === 'DELETE') {
      return deleteBookmark(match[1]);
    }

    if (path === '/') {
      return Response.json({
        name: 'Bookmark API',
        endpoints: ['GET /bookmarks', 'POST /bookmarks', 'GET /bookmarks/:id', 'DELETE /bookmarks/:id']
      });
    }

    return Response.json({ error: 'Not Found' }, { status: 404 });
  },
};

function listBookmarks(): Response {
  const all = Array.from(bookmarks.values());
  return Response.json({ bookmarks: all, count: all.length });
}

async function createBookmark(request: Request): 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(),
  };

  bookmarks.set(id, bookmark);
  return Response.json(bookmark, { status: 201 });
}

function getBookmark(id: string): Response {
  const bookmark = bookmarks.get(id);
  if (!bookmark) {
    return Response.json({ error: 'Bookmark not found' }, { status: 404 });
  }
  return Response.json(bookmark);
}

function deleteBookmark(id: string): Response {
  if (!bookmarks.has(id)) {
    return Response.json({ error: 'Bookmark not found' }, { status: 404 });
  }
  bookmarks.delete(id);
  return Response.json({ message: 'Bookmark deleted' });
}