Designing a Complete CRUD API

intermediate
13 min

Designing a Complete CRUD API

You now know methods, status codes, URLs, and pagination as separate skills. This lesson assembles them into a single coherent design: a complete CRUD (Create, Read, Update, Delete) API for a task management product, specified endpoint by endpoint the way a professional team would before writing any code.

Step 1: Identify Resources and Fields

Our product has projects containing tasks. That gives two resources. For each, define the representation up front:

json
{ "id": "t_9f2a", "projectId": "p_114", "title": "Ship onboarding email", "status": "todo", "priority": 3, "dueDate": "2026-08-01", "createdAt": "2026-07-20T10:04:00Z", "updatedAt": "2026-07-20T10:04:00Z" }

Decisions worth making now, not later: prefixed string IDs (t_, p_) that never leak row counts, server-managed timestamps the client cannot set, and a status field constrained to an enum (todo, in_progress, done).

Step 2: The Endpoint Table

Write the full surface as a table before implementing anything. This is your contract.

text
POST /api/v1/projects create project 201 + body GET /api/v1/projects list projects 200, paginated GET /api/v1/projects/{id} read one project 200 or 404 PATCH /api/v1/projects/{id} update project 200 or 404/422 DELETE /api/v1/projects/{id} delete project 204 or 404 POST /api/v1/projects/{id}/tasks create task in project GET /api/v1/projects/{id}/tasks list tasks (filter: ?status=&sort=) GET /api/v1/tasks/{id} read task directly PATCH /api/v1/tasks/{id} update task fields DELETE /api/v1/tasks/{id} delete task

Note the pattern from the URL design lesson: tasks are created and listed under their project, but addressed directly once they exist.

Step 3: Specify Each Interaction Precisely

For creation, define exactly which fields are required (title), optional (priority, dueDate), and forbidden (id, createdAt). A request with unknown fields should be rejected with 400 or have them ignored; pick one and document it. The response is 201 with a Location header and the complete created object, so clients never need a follow-up GET.

For updates, prefer PATCH with partial bodies for ergonomics. Enforce transitions where the business demands it: moving a task from done back to todo might be allowed, but deleting a project with active tasks might return 409 Conflict, forcing clients to confirm by deleting tasks first or passing ?force=true.

For reads, apply the previous lesson directly: default sort by -createdAt, filter by status and priority, paginate with limit and cursor or page. For deletes, decide between hard delete (row removed, 204) and soft delete (status becomes archived). Soft delete is safer for user-facing products because it enables undo and audit trails; if you soft delete, exclude archived items from default listings.

Step 4: Consistent Error Shape

Every error, from any endpoint, should share one envelope:

json
{ "error": { "code": "validation_failed", "message": "title is required", "details": [{ "field": "title", "issue": "required" }] } }

Clients then write their error handling exactly once. We will go deeper on this in the error handling lesson.

Step 5: Walk the Lifecycle

Before coding, dry-run one full story: create project (201), create three tasks (201 each), list them (200, three items), patch one to done (200), delete it (204), delete it again (404), list again (two items). If every step has an obvious status code and body in your spec, the design is complete. If you hesitate anywhere, the spec has a hole that would otherwise surface as an argument mid-implementation.

Key Takeaways

  • Design the resource representation and full endpoint table before writing code.
  • Create and list nested under the parent; read, update, and delete by direct ID.
  • Decide validation rules, deletion semantics, and state transitions explicitly.
  • One consistent error envelope across all endpoints multiplies client productivity.

Next up: Testing APIs with Postman and curl, where we exercise designs like this one by hand.

Designing a Complete CRUD API - REST APIs | CodeYourCraft | CodeYourCraft