Express Router — Why Use It
When all your routes are in a single app.js file, the file quickly becomes thousands of lines long and unmaintainable. Express Router allows you to split routes into separate files based on resource or feature, then mount them in app.js.
Benefits:
- Separation of concerns: user routes, video routes, auth routes in separate files
- Modularity: each route file is independently testable
- Cleaner app.js: just mounts routers, no individual route definitions
- Middleware scoping: apply middleware only to specific routers
---
Controller Pattern
A controller is a function that handles a specific route. Following the Single Responsibility Principle, each controller does exactly one thing. Controllers should:
- Extract data from request (body, params, query)
- Validate inputs
- Call service/model layer
- Send response
---
Route Parameters, Query Strings, and Body
// Route parameter: /api/users/:id
// Access via: req.params.id
router.get('/users/:id', getUser);
// Query string: /api/videos?page=1&limit=10&sortBy=views
// Access via: req.query.page, req.query.limit, req.query.sortBy
router.get('/videos', getVideos);
// Request body: POST /api/users { name, email, password }
// Access via: req.body.name, req.body.email
// Requires: app.use(express.json())
router.post('/users', createUser);
---
express.json() and express.urlencoded()
These two middleware functions are essential:
app.use(express.json()); // Parse JSON body
app.use(express.urlencoded({ extended: true })); // Parse form data
Without express.json(), req.body will be undefined for JSON POST requests. Without express.urlencoded(), HTML form submissions will not be parsed.
---
Morgan Logger Setup
Morgan logs every incoming HTTP request — method, URL, status code, response time:
npm install morgan
import morgan from 'morgan';
app.use(morgan('dev'));
// Output: GET /api/users 200 45.123 ms - 256
// Formats: 'dev', 'combined', 'common', 'short', 'tiny'
---
Debugging Techniques
1. console.log req.body/params/query:
const createUser = asyncHandler(async (req, res) => {
console.log('req.body:', req.body);
console.log('req.params:', req.params);
console.log('req.query:', req.query);
console.log('req.headers:', req.headers);
// ... rest of controller
});
2. Postman debugging:
- Create a Collection for your API
- Create Environment with
{{baseUrl}}=http://localhost:5000 - Set
Content-Type: application/jsonin request headers - Check Response Body, Status, Headers, and Cookies
- Use Postman Console (View → Console) to see request details
3. Reading stack traces: When your server throws an error, the stack trace shows which file and line caused it. Always read from top to bottom — the first line after "Error:" shows the actual problem.
---
Route Ordering Importance
Express matches routes in the order they are defined. Specific routes must come before generic ones:
// ✅ Correct order:
router.get('/users/me', getMe); // Specific — matches first
router.get('/users/:id', getUserById); // Generic — matches :id
// ❌ Wrong order:
router.get('/users/:id', getUserById); // 'me' would match as id='me'
router.get('/users/me', getMe); // Never reached
---
Complete User Routes and Controller Example
userRoutes.js:
import { Router } from 'express';
import {
getAllUsers, createUser, getUserById,
updateUser, deleteUser
} from '../controllers/user.controller.js';
import { verifyJWT } from '../middlewares/auth.middleware.js';
const router = Router();
router.route('/').get(getAllUsers).post(createUser);
router.route('/:id')
.get(verifyJWT, getUserById)
.put(verifyJWT, updateUser)
.delete(verifyJWT, deleteUser);
export { router as userRouter };
Mount in app.js:
app.use('/api/v1/users', userRouter);
// Now: GET /api/v1/users, POST /api/v1/users, GET /api/v1/users/:id, etc.