beginner·7 min read·Updated May 1, 2026

REST CRUD Explained: Create, Read, Update, Delete Walkthrough

CRUD is the heartbeat of REST. Create, Read, Update, Delete — walked through end to end.

The four verbs, one after another

CRUD stands for Create, Read, Update, Delete — the four operations every data-driven application performs. In REST, each maps to an HTTP method:

OperationMethodURLBodySuccess status
CreatePOST/usersfull user201 Created
Read (list)GET/usersnone200 OK
Read (one)GET/users/{id}none200 OK (or 404)
Update (partial)PATCH/users/{id}changed fields200 OK
Update (full)PUT/users/{id}full user200 OK
DeleteDELETE/users/{id}none204 No Content

Let's walk through the full cycle.

Step 1 — Create

POST/api/v1/users
Step 1 — Create a user.
curl -X POST 'https://demo.totalshiftleft.ai/api/v1/users' \
  -H 'Content-Type: application/json' \
  -d '{"name":"Alice","email":"alice@example.com","role":"user"}'

Things to notice:

  • Status is 201 Created, not 200.
  • The response echoes the created resource, including a server-generated id (UUID) and created_at timestamp.
  • Copy the id from the response — you'll need it for steps 3 and 4.

Step 2 — Read

GET/api/v1/users
Step 2 — Read the list to see the user you just created.
curl -X GET 'https://demo.totalshiftleft.ai/api/v1/users'

This lists all users in your sandbox session. The user you created in step 1 is there. Single-resource reads use GET /users/{id} — try changing the path to read just your user by ID.

A detail that catches people out: an empty list [] from a search or filter is success. Only return 404 when the specific resource at that URL genuinely doesn't exist. GET /users?role=banana matching nothing should return 200 [] with pagination metadata.

Step 3 — Update (PATCH)

Replace {id} in the widget below with the UUID from step 1, then click Run:

PATCH/api/v1/users/{id}
Step 3 — Update the role. Replace {id} with the UUID from step 1.
curl -X PATCH 'https://demo.totalshiftleft.ai/api/v1/users/{id}' \
  -H 'Content-Type: application/json' \
  -d '{"role":"admin"}'

You sent only { "role": "admin" }. The response shows the full user — but only the role changed. Name, email, and other fields are preserved. That's PATCH.

If you used PUT, you'd need to send the complete user object. Miss the email? It might be blanked out or the request might 400 depending on the API.

Step 4 — Delete

Again, replace {id}:

DELETE/api/v1/users/{id}
Step 4 — Delete. Replace {id} with the UUID from step 1.
curl -X DELETE 'https://demo.totalshiftleft.ai/api/v1/users/{id}'
  • Status is 204 No Content. No response body.
  • If you try to GET the same user ID now, you'll get 404.
  • If you try to DELETE the same user again, most well-designed APIs return 404 or 204 (both are defensible — DELETE is idempotent, so "already gone" and "just deleted" are the same outcome).

What a full CRUD test suite should cover

For every resource in your API, a thorough test suite covers:

  1. Happy paths — one successful call per method.
  2. Not found — GET/PATCH/PUT/DELETE on a non-existent ID returns 404.
  3. Validation errors — POST/PATCH/PUT with invalid body returns 400 with field-level details.
  4. Duplicates — POST with duplicate unique fields (email) returns 409.
  5. Auth — every write method requires authentication; assert 401 without a token.
  6. Authorization — a non-admin trying to create an admin returns 403.

For a single resource type, that's ~20 test cases. Multiply by every resource in your API. This is exactly where test-generation from OpenAPI saves most of the work.

Common CRUD bugs

1. PATCH that silently ignores unknown fields. If you PATCH { "roel": "admin" } (typo), a lot of APIs just ignore it. The user expects their role to change; it doesn't. Harder to catch than an explicit error. Well-designed APIs 400 on unknown fields.

2. Update returning stale data. Some APIs cache the resource and return the pre-update state after a successful PATCH. The write succeeded, but the response shows the old data. Test explicitly that the fields you changed are reflected in the response.

3. DELETE that doesn't actually delete. Soft deletes (setting deleted_at instead of actually removing rows) are fine — but the behavior must be consistent. GET /users/123 after DELETE should return 404, even if the row still exists in the database with a tombstone flag.

What's next

You've done the CRUD cycle. Next lesson: PATCH vs PUT — the difference that trips everyone up — a deep dive on the single most confused pair of HTTP methods.

Related lessons

Read more on the blog