This lesson builds a complete CRUD (Create, Read, Update, Delete) feature end-to-end, wiring a controller to a service, using DTOs for input, and returning appropriate status codes for each operation.
The Complete CRUD Controller
A CRUD resource needs five operations: list, read one, create, update, and delete. In NestJS, the controller declares the routes and delegates each operation to the service, keeping the controller focused purely on request/response shape.
Every method here follows the same shape: extract input with decorators, delegate to the service, return the result. The service owns all actual persistence logic.
The service owns the data store (here, an in-memory array; in a real app, a database via a repository).
findOne should throw NotFoundException when the id doesn't exist, rather than returning undefined.
create typically returns the newly created resource, including its generated id.
remove commonly returns 204 No Content with an empty body.
CRUD Endpoint Cheatsheet
The five standard operations and their expected outcomes.
Operation
Route
Success Status
Typical Failure
List
GET /cats
200
—
Read one
GET /cats/:id
200
404 if missing
Create
POST /cats
201
400 on invalid body
Update
PATCH /cats/:id
200
404 if missing
Delete
DELETE /cats/:id
204
404 if missing
Handling Missing Resources Consistently
Every read, update, and delete operation on a single resource needs the same "not found" handling. Centralizing this in a small private helper inside the service keeps the behavior consistent and avoids repeating the same check five times.
private findOrThrow(id: number): Cat {
const cat = this.cats.find((c) => c.id === id);
if (!cat) {
throw new NotFoundException(`Cat #${id} not found`);
}
return cat;
}
Moving From In-Memory to a Real Database
The in-memory array in this lesson is only for illustrating the CRUD flow clearly. In a real application, the same service methods would call into a repository backed by TypeORM, Prisma, or Mongoose, without changing the controller at all, since the controller only depends on the service's public method signatures.
Common Mistakes
Returning undefined or null for a missing resource instead of throwing NotFoundException.
Reusing the same DTO for create and update without a PartialType() wrapper.
Forgetting @HttpCode(204) on delete, leaving Nest's default 200 status instead.
Putting the in-memory array directly inside the controller instead of the service.
Key Takeaways
A complete CRUD feature needs list, read-one, create, update, and delete operations.
The controller stays thin, delegating persistence and business rules to the service.
Consistent "not found" handling avoids repeating the same check in every method.
The same controller works unchanged whether the service uses an array or a real database.
Pro Tip
Write the findOrThrow-style helper for "not found" handling on day one of any CRUD service, even before adding a real database, retrofitting consistent error handling across five already-written methods is far more tedious than building it in from the start.
You now have a complete CRUD API pattern. Next, learn about DTOs in more depth, the classes that shape your API's input and output.