All posts
4 min read

What Is CRUD? The Four Operations Behind Almost Every App

Create, Read, Update, Delete: most app features are some combination of these four. What CRUD means, how it maps to SQL and HTTP methods, what a CRUD API looks like, and the details — validation, permissions, pagination — that separate a demo from a real app.

getting starteddatabasesno-codebeginner

Look at almost any app — a to-do list, a CRM, an online shop, a blog — and most of what it does is four things: create something, read it, update it, delete it. Developers call this CRUD, and once you see it, you'll see it everywhere.

The four operations

Letter Operation Example in a to-do app
C Create Add a new task
R Read See your list; open one task
U Update Rename a task; mark it done
D Delete Remove a task

A "CRUD app" is one that's mostly these four operations on a few kinds of data. That's not an insult — it describes most useful software.

How CRUD maps to databases and APIs

The same four operations appear at every layer of an app:

CRUD SQL HTTP method Example API route
Create INSERT POST POST /api/tasks
Read SELECT GET GET /api/tasks, GET /api/tasks/42
Update UPDATE PUT / PATCH PATCH /api/tasks/42
Delete DELETE DELETE DELETE /api/tasks/42

When you click "Save" on an edited task, the browser sends a PATCH to the API, which runs an UPDATE on the database. Same idea, three layers. (What Is an API? and SQL for Beginners.)

PUT traditionally means "replace the whole thing," and PATCH means "change these fields." Many apps just use one; what matters is consistency. (Designing a REST API.)

What a simple CRUD API looks like

In an Express server:

app.get("/api/tasks", async (req, res) => {
  const tasks = await db.task.findMany({ where: { userId: req.user.id } })
  res.json(tasks)
})

app.post("/api/tasks", async (req, res) => {
  const task = await db.task.create({
    data: { title: req.body.title, userId: req.user.id },
  })
  res.status(201).json(task)
})

app.patch("/api/tasks/:id", async (req, res) => { /* … */ })
app.delete("/api/tasks/:id", async (req, res) => { /* … */ })

AI tools generate code like this very quickly. Getting it working is easy. The details below are what make it correct.

What makes CRUD harder than it looks

Permissions on every operation

The most important detail and the most commonly missed. It's not enough that the user is logged in — each operation must check that this user may touch this row.

Notice where: { userId: req.user.id } above. Without it, GET /api/tasks returns everyone's tasks. And for update and delete:

// ❌ Anyone logged in can delete any task by changing the ID
await db.task.delete({ where: { id: req.params.id } })

// ✅ Only the owner's task matches
await db.task.deleteMany({ where: { id: req.params.id, userId: req.user.id } })

(Authentication vs Authorization.)

Validation

Never trust what the browser sends. Check that required fields exist, text isn't absurdly long, numbers are in range, and users can't set fields they shouldn't — like isAdmin or price. (Form Validation Explained.)

Reading lots of data

"Read" often means "read a list." A list that returns every row works with 20 tasks and fails with 200,000. Real lists need pagination — returning a page at a time — and usually sorting and filtering. (API Pagination.)

Deleting safely

Does deleting a user delete their tasks? Their invoices? Decide deliberately. Many apps use soft deletes — marking rows as deleted rather than removing them — so mistakes can be undone and history is kept. (Soft Deletes and Audit Logs.)

Concurrent updates

Two people edit the same record at once; the second save silently overwrites the first. For collaborative apps, you need a way to detect it — like checking an updated_at value hasn't changed since the edit began.

Returning the right status codes

201 after create, 404 when the thing doesn't exist (or isn't theirs), 400 for bad input. (HTTP Status Codes Explained.)

A CRUD checklist

For each kind of data in your app:

  • Create, read, update, and delete each check the user owns (or may access) the row
  • Input is validated on the server
  • Users can't set protected fields
  • Lists are paginated
  • Deleting is deliberate: cascade, block, or soft delete
  • Errors return sensible status codes

It's a useful checklist to hand to your AI tool, too: "Review every CRUD endpoint against this list."


EasySpawn gives Claude Code a real backend and managed Postgres in one workspace, so your CRUD endpoints can be tested against actual data and actual permission checks before launch. See how it works or join the waitlist.

Related: What Is an API? · Design Your First Database · GraphQL vs REST

Keep reading