Bring it all together — build a professional REST API with Express routes, Sequelize models, pagination, filtering, and proper error handling.
CRUD Review
Controllers
Services
Pagination & Filtering
Error Handling
Quiz
CRUD Review
Remember our restaurant? Now we're building the entire kitchen system. The controller is the waiter (takes orders, delivers food). The service is the chef (does the actual cooking/logic). The model is the recipe book (defines what each dish looks like). They work together to handle every customer request.
Here's how CRUD maps to HTTP methods and Sequelize operations:
Operation
HTTP
Route
Sequelize
Restaurant
Create
POST
/api/tasks
Task.create()
Place a new order
Read All
GET
/api/tasks
Task.findAll()
See the full menu
Read One
GET
/api/tasks/:id
Task.findByPk()
Ask about a specific dish
Update
PUT
/api/tasks/:id
task.update()
Change your order
Delete
DELETE
/api/tasks/:id
task.destroy()
Cancel your order
Project Structure
Project — Backend API Structure
Controllers
The controller is the waiter. It takes the customer's request (req), calls the kitchen (service), and delivers the response (res). The waiter doesn't cook — they just coordinate between the customer and the kitchen.
controllers/taskController.js
Routes — Connecting URLs to Controllers
routes/taskRoutes.js
Notice the clean separation: Routes just map URLs to controller functions. Controllers extract data from requests and call services. This makes each piece easy to test and modify independently.
Services
The service is the chef. It contains the actual business logic — the "how to cook the dish." The service talks to the database (model) and returns the result to the controller (waiter). The chef doesn't care about the customer (req/res) — they only care about cooking (data operations).
services/taskService.js
Always filter by userId! Notice every query includes where: { id, userId }. This ensures users can only access their OWN tasks. Without this check, any user could read/edit/delete anyone's data — a major security hole.
Pagination & Filtering
Imagine a library with 10,000 books. You wouldn't dump all 10,000 on the floor — you'd show them page by page (pagination) and let users search by genre or author (filtering). The same principle applies to APIs — don't send all records at once, let clients request specific pages and filters.
How Pagination Works
Pagination — How It Works
Filtering and Sorting
Sequelize — Filtering & Sorting
Sequelize Operators Reference
Operator
Meaning
Example
Op.eq
Equal
{ age: { [Op.eq]: 25 } }
Op.ne
Not equal
{ status: { [Op.ne]: 'deleted' } }
Op.gt
Greater than
{ age: { [Op.gt]: 18 } }
Op.gte
Greater than or equal
{ price: { [Op.gte]: 10 } }
Op.lt
Less than
{ age: { [Op.lt]: 65 } }
Op.like
Pattern match
{ name: { [Op.like]: '%raj%' } }
Op.iLike
Case-insensitive match
{ name: { [Op.iLike]: '%raj%' } }
Op.in
In a list
{ id: { [Op.in]: [1, 2, 3] } }
Op.or
OR condition
{ [Op.or]: [{...}, {...}] }
Error Handling
Good error handling is like a good hospital. When something goes wrong, you want clear diagnosis (error type), helpful treatment (error message), and proper records (logging). Bad error handling is like a doctor saying "something is wrong" with no further details — useless!
Custom Error Class
utils/AppError.js — Custom Error Class
Centralized Error Handler
middleware/errorHandler.js
Error handling best practices:
1. Use custom error classes (AppError) for expected errors
2. Handle Sequelize-specific errors (validation, unique constraint)
3. Never expose stack traces in production
4. Always log errors server-side for debugging
5. Return helpful, specific messages to the client
📝 Chapter 14 Quiz
1. What's the difference between a controller and a service?
Controllers are for GET, services are for POST
Controllers handle req/res, services handle business logic
There is no difference
Services are for frontend, controllers for backend
2. For page 3 with 10 items per page, what is the offset?
3
10
20
30
3. Which operator performs case-insensitive search in Sequelize?
Op.iLike
Op.search
Op.contains
Op.match
4. Why should service methods always filter by userId?
To make queries faster
To sort the results
It's not necessary, it's optional
To ensure users only access their own data (security)