Pathubs Logo Mark
PATHUBSFREE CAREER ROADMAPS
HomeExploreDiscoverCompare ⚖️My Progress 📊Support
Student Support & Feedback

Have Questions or Need Help?

Have questions, feedback, or suggestions for new roadmaps and interactive tools? Reach out to our team — we review every message to make practical learning better for everyone.

supportpathubs@gmail.com Official Telegram Support (@PathubsSupport)
Pathubs

100% Free, Zero-Paywall Tech Career Roadmaps, In-Depth Practical Content, and Live Interactive Virtual Labs for Learners Worldwide.

Popular Careers

  • Frontend Development
  • Backend Development
  • AI & LLM Engineering
  • Full Stack Web Dev
  • Data Analytics

Platform Tools

  • Career Discovery Quiz
  • Compare Careers

Contact & Info

  • About Us
  • supportpathubs@gmail.com
  • Support Pathubs

© 2026 Pathubs. All Rights Reserved. Structured learning, practical content, and hands-on practice for learners worldwide.

AboutPrivacy PolicyTerms & ConditionsSitemapRobots
Home/Full Stack Web Development/Server & Request Handling/Controllers
Server & Request HandlingExpress 5.x & FastAPICode Refactoring LabSeparation of Concerns

Controllers — Separating Request Handling from Application Logic

Master professional backend code organization. Learn why cramming database queries, validation rules, and business logic into route definitions creates bloated, untestable applications. Discover how controllers isolate HTTP transport mechanics, delegate domain operations to dedicated services, and engage in an authentic hands-on Code Refactoring Lab.

Node.js Standard
Express 5.x Controller functions
Python Standard
FastAPI path handlers + Services
Core Responsibility
Translate HTTP req → Service → res
Architectural Impact
Zero Fat Routes / High Testability
Structured Curriculum Outline (10 Sections)
1What is a Controller?Concept2Before vs After (Tasks API)Pattern3Controller ResponsibilitiesDOs & DON'Ts4Real Full Stack ArchitectureStructure5Actual Code Refactoring LabHands-On6Express 5.x vs FastAPIComparison7Debugging & 4 Common MistakesGotchas8Mini Challenge: Multi-ResourceChallenge9Full Stack Request LifecycleRecap10Controllers Mastery QuizExam
1

What is a Controller?

Understanding the controller's role as the intermediary translating HTTP requests into application operations.

The Request-Response Separation of Concerns

When building backend applications, beginners often place all logic directly inside their route callbacks. While this works for tiny 5-line tutorials, real-world backends rapidly become unmaintainable when routing, input validation, business calculations, SQL queries, and status formatting are tangled together.

Client Request
POST /api/tasks
→
Route Layer
"Which handler?"
→
Controller Layer
"Handle HTTP req/res"
→
Service Layer
"Business operation"
→
Database Layer
PostgreSQL / MySQL
Route Responsibility
"Where does this go?"

Binds HTTP method + path to a handler. Contains zero business logic or database queries.

Controller Responsibility
"How do we handle the HTTP dialogue?"

Extracts req.params and req.body, invokes services, selects HTTP status (200, 201, 400), and sends JSON.

Service Responsibility
"What application operation happens?"

Pure business logic (e.g. creating records, checking permissions, sending emails) completely free of Express or HTTP objects.

2

Before vs After: Refactoring a Tasks API

Observe the dramatic improvement in readability, maintainability, and testing when fat routes are refactored.

❌ BEFORE: Bloated "Fat Route"routes/taskRoutes.js
// ❌ Fat route: Everything mashed together!
router.post('/tasks', async (req, res) => {
  const { title, priority } = req.body;

  // Validation mixed with route
  if (!title || title.trim().length === 0) {
    return res.status(400).json({ error: 'Title required' });
  }

  // Business rules mixed in
  const taskPriority = priority || 'medium';

  // Direct database query mixed in
  const result = await db.query(
    'INSERT INTO tasks (title, priority) VALUES ($1, $2) RETURNING *',
    [title, taskPriority]
  );

  // Response formulation
  return res.status(201).json(result.rows[0]);
});
✅ AFTER: Clean Route + Controllerroutes/taskRoutes.js & controllers/taskController.js
// routes/taskRoutes.js
// ✅ Route is one clean line!
router.post('/tasks', createTask);

// controllers/taskController.js
// ✅ Controller handles only HTTP transport concerns!
async function createTask(req, res) {
  const { title, priority } = req.body;
  if (!title?.trim()) {
    return res.status(400).json({ error: 'Title required' });
  }

  // Delegate domain operation to service
  const task = await taskService.createTask({ title, priority });
  return res.status(201).json(task);
}
3

Controller Responsibilities: The DOs and DON'Ts

Clear boundaries establishing what belongs in the controller layer versus what belongs in services or models.

✅ What BELONGS in a Controller

  • Extracting Request Inputs: Reading req.params, req.query, and req.body.
  • Basic Input Validation: Confirming required parameters are present before invoking services.
  • Delegating to Services: Calling business operations (e.g. taskService.createTask(...)).
  • Selecting HTTP Status Codes: Choosing 200 OK, 201 Created, 400 Bad Request, or 404 Not Found.
  • Setting Response Headers: Setting content-type or custom metadata headers.
  • Error Delegation: Passing unhandled exceptions to centralized error middleware with next(err).

🚫 What DOES NOT Belong in a Controller

  • Direct Database SQL Queries: Writing SELECT * FROM tasks directly in the handler.
  • Complex Business Logic: Formulas like calculating tax, discounts, or processing payment gateways.
  • Low-level Cryptography: Writing bcrypt hashing algorithms inline.
  • Third-party API Calls: Directly invoking external Stripe or SendGrid SDKs inside the route handler.
  • Repeated Utility Functions: String formatting or date calculation functions.
4

Real Full Stack Architecture Example: Task API

A professional project layout separating routes, controllers, and services in a production-ready structure.

Scalable Folder StructureProject Root
src/
├── routes/             # Maps URLs & HTTP Verbs to Controllers
│   └── taskRoutes.js   # router.post('/tasks', createTask);
│
├── controllers/        # Translates HTTP requests & formats HTTP responses
│   └── taskController.js# Extracts params, calls taskService, returns res.json()
│
├── services/           # Contains pure domain & business logic
│   └── taskService.js  # Calculates status, coordinates DB updates
│
├── models/             # Database schemas & direct queries
│   └── taskModel.js    # PostgreSQL / MySQL queries or ORM models
│
└── app.js              # Initializes Express, mounts routers
5

Actual Code Refactoring Lab (Main Activity)

Edit real backend files below. Refactor the working but bloated route in routes/taskRoutes.js into a clean route + controller layer in controllers/taskController.js.

Backend Refactoring Environment

Edit the files below to decouple route binding from controller execution.

Backend Execution Trace1 Events
[00:00:00]Refactoring Lab ready. Click "Test API (PATCH /api/tasks/42)" to test current code.
6

Express 5.x vs FastAPI Implementation

Controllers are an architectural responsibility, not a mandatory framework class. See how both frameworks implement this pattern.

Express 5.x (Node.js)controllers/taskController.js
// Express Controller function
async function getTaskById(req, res) {
  const { id } = req.params;
  const task = await taskService.find(id);
  if (!task) return res.status(404).json({ error: 'Not found' });
  return res.status(200).json(task);
}

module.exports = { getTaskById };

// Wire in routes/taskRoutes.js:
// router.get('/tasks/:id', getTaskById);
FastAPI (Python)controllers/task_controller.py
# FastAPI path operation function acts as controller
from fastapi import APIRouter, HTTPException
from services import task_service

router = APIRouter()

@router.get("/tasks/{task_id}")
async def get_task(task_id: int):
    task = await task_service.find(task_id)
    if not task:
        raise HTTPException(status_code=404, detail="Not found")
    return task
7

Debugging & 4 Common Controller Mistakes

Inspect real-world controller bugs, examine error signatures, and learn precise production fixes.

1. Controller Function Not Exported

Symptom: TypeError: Route.get() requires a callback function but got a [object Undefined]
❌ Buggy Code
// ❌ BUGGY: Forgot module.exports!
async function getTask(req, res) {
  res.json({ id: 1, title: 'Learn Controllers' });
}
// Missing: module.exports = { getTask };
✅ Fixed Code
// ✅ FIXED: Export the controller function
async function getTask(req, res) {
  res.json({ id: 1, title: 'Learn Controllers' });
}
module.exports = { getTask };

Why this happens: In Node.js, functions defined in a file are private to that module unless explicitly exported via module.exports or export. Requiring an unexported function returns undefined, causing Express to throw a TypeError during server startup.

8

Mini Challenge: Multi-Resource Refactoring

A Learning Platform API has 3 bloated endpoints: GET /api/courses, POST /api/enrollments, and PATCH /api/users/:id.

Inspect how the three endpoints map cleanly across the architectural boundaries:

GET /api/coursesCatalog Query
routes: router.get('/courses', getCourses) → controller: courseController.getCourses → service: courseService.list()
POST /api/enrollmentsRegistration
routes: router.post('/enrollments', enrollStudent) → controller: enrollController.create → service: enrollService.register()
PATCH /api/users/:idProfile Update
routes: router.patch('/users/:id', updateUser) → controller: userController.update → service: userService.updateProfile()
9

Full Stack Request Lifecycle & Recap

Summary of the 4 core backend layers and their strict boundaries.

LayerCore Question AnsweredKey ResponsibilityExample Code
Route"Which code handles this URL + method?"URL matching, HTTP verb bindingrouter.get('/tasks', getTasks)
Controller"How do we process the HTTP dialogue?"Extract params, invoke service, pick status coderes.status(200).json(task)
Service"What business operation needs to happen?"Calculations, validations, domain rulestaskService.completeTask(id)
Database"Where is data stored and retrieved?"SQL execution, persistence, indexingSELECT * FROM tasks WHERE id = $1
TEST YOUR KNOWLEDGE

Controllers Mastery Quiz

Test your understanding of backend controller architecture, separation of concerns, and Express 5/FastAPI patterns.

Question 1 of 10Score: 0 / 10

🌐 What is the primary architectural purpose of a Controller in a backend web application?

🧠 The 5 Golden Rules of Backend Controllers

1. Keep Controllers Thin
Controllers translate HTTP to domain calls. If a controller is writing SQL or complex math, move it to a service or model.
2. Routes Are Just Maps
Routes answer "Which URL and method?". A route file should be clean one-liners binding URLs to controller handlers.
3. Services Are Framework-Free
Services should not import Express or know about req/res. This makes them 100% reusable and easily unit-tested.
4. Always Formulate HTTP Response
The controller chooses the HTTP status code (200, 201, 400, 404) and formats the JSON envelope returned to the client.