Controllers
Controllers are classes that organize related routes together. They use the @Controller decorator to define a base path for all routes within the class.
Creating a Controller
Use the @Controller(basePath) decorator to create a controller:
import { Controller, Get, Post } from 'muzu';
@Controller('/api/users')
class UserController {
@Get()
getAllUsers() {
return { users: [] };
}
@Get(':id')
getUser(req: Request) {
return { id: req.params.id };
}
@Post()
createUser(req: Request) {
return { created: true };
}
}
In this example:
GET /api/users→getAllUsers()GET /api/users/123→getUser()POST /api/users→createUser()
Controller Registration
Controllers are automatically registered when their decorators execute:
import { MuzuServer, Controller, Get } from 'muzu';
// Controllers register themselves when @Controller executes
@Controller('/api')
class ApiController {
@Get('/status')
getStatus() {
return { status: 'ok' };
}
}
// Create server - controllers are already registered
const app = new MuzuServer();
// Controllers are loaded when listen() is called
app.listen(3000);
You don't need to manually register controllers. The @Controller decorator automatically adds the controller to a global registry, and all controllers are loaded when app.listen() is called.
Multiple Controllers
You can create multiple controllers for different resources:
@Controller('/api/users')
class UserController {
@Get()
getUsers() {
return { users: [] };
}
@Post()
createUser(req: Request) {
return { created: true };
}
}
@Controller('/api/products')
class ProductController {
@Get()
getProducts() {
return { products: [] };
}
@Get(':id')
getProduct(req: Request) {
return { product: {} };
}
}
@Controller('/api/orders')
class OrderController {
@Get()
getOrders() {
return { orders: [] };
}
@Post()
createOrder(req: Request) {
return { order: {} };
}
}
Controller Organization
Organize controllers by resource or feature:
Resource-Based Organization
// user.controller.ts
@Controller('/api/users')
export class UserController {
@Get()
getAll() { /* ... */ }
@Get(':id')
getById() { /* ... */ }
@Post()
create() { /* ... */ }
@Put(':id')
update() { /* ... */ }
@Delete(':id')
delete() { /* ... */ }
}
// product.controller.ts
@Controller('/api/products')
export class ProductController {
@Get()
getAll() { /* ... */ }
@Get(':id')
getById() { /* ... */ }
}
Feature-Based Organization
// auth.controller.ts
@Controller('/auth')
export class AuthController {
@Post('login')
login() { /* ... */ }
@Post('register')
register() { /* ... */ }
@Post('logout')
logout() { /* ... */ }
}
// profile.controller.ts
@Controller('/profile')
export class ProfileController {
@Get()
getProfile() { /* ... */ }
@Put()
updateProfile() { /* ... */ }
}
Controller with Services
Controllers should handle HTTP logic, delegate business logic to services:
// user.service.ts
class UserService {
async findAll() {
return await database.users.findAll();
}
async findById(id: string) {
return await database.users.findById(id);
}
async create(data: any) {
return await database.users.create(data);
}
}
// user.controller.ts
const userService = new UserService();
@Controller('/api/users')
class UserController {
@Get()
async getUsers() {
const users = await userService.findAll();
return { users };
}
@Get(':id')
async getUser(req: Request) {
const user = await userService.findById(req.params.id);
if (!user) {
throw new NotFoundException('User not found');
}
return user;
}
@Post()
async createUser(req: Request) {
const user = await userService.create(req.body);
return user;
}
}
Controller Base Path
The controller base path is combined with route paths:
@Controller('/api/v1/users')
class UserController {
@Get() // GET /api/v1/users
getAll() { /* ... */ }
@Get(':id') // GET /api/v1/users/:id
getById() { /* ... */ }
@Get(':id/posts') // GET /api/v1/users/:id/posts
getUserPosts() { /* ... */ }
}
Nested Routes
Create nested resource routes:
@Controller('/api/users')
class UserController {
@Get(':userId/posts')
getUserPosts(req: Request) {
const { userId } = req.params;
return { userId, posts: [] };
}
@Get(':userId/posts/:postId')
getUserPost(req: Request) {
const { userId, postId } = req.params;
return { userId, postId, post: {} };
}
@Get(':userId/posts/:postId/comments')
getPostComments(req: Request) {
const { userId, postId } = req.params;
return { userId, postId, comments: [] };
}
}
Controller Lifecycle
- Class Definition: Decorators execute when class is defined
- Controller Registration:
@Controlleradds class to global registry - Server Start:
app.listen()processes all registered controllers - Route Compilation: Each route is compiled with optimized metadata
- Request Handling: Routes are matched using Radix tree
// 1. Class defined, decorators execute
@Controller('/api')
class ApiController {
@Get('/hello')
hello() {
return { message: 'Hello' };
}
}
// 2. Controller is now in global registry
const app = new MuzuServer();
// 3. listen() processes all controllers
app.listen(3000);
// 4. Routes are now compiled and ready
// 5. Server handles incoming requests
Testing Controllers
Clear registry between tests:
import { clearRegistry } from 'muzu';
describe('UserController', () => {
beforeEach(() => {
clearRegistry(); // Clear controller registry
});
it('should get users', () => {
@Controller('/users')
class UserController {
@Get()
getUsers() {
return { users: [] };
}
}
const app = new MuzuServer();
app.listen(3000);
// Test the controller...
});
});
Best Practices
Keep Controllers Thin
Controllers should only handle HTTP concerns:
// ✅ Good - Controller is thin
@Controller('/users')
class UserController {
@Get()
async getUsers() {
const users = await userService.findAll();
return { users };
}
}
// ❌ Bad - Controller has business logic
@Controller('/users')
class UserController {
@Get()
async getUsers() {
const users = await database.query('SELECT * FROM users');
const filtered = users.filter(u => u.active);
const sorted = filtered.sort((a, b) => a.name.localeCompare(b.name));
return { users: sorted };
}
}
Use Meaningful Names
// ✅ Good - Clear method names
@Controller('/products')
class ProductController {
@Get()
getAllProducts() { /* ... */ }
@Get(':id')
getProductById() { /* ... */ }
@Post()
createProduct() { /* ... */ }
}
// ❌ Bad - Unclear method names
@Controller('/products')
class ProductController {
@Get()
handler1() { /* ... */ }
@Get(':id')
handler2() { /* ... */ }
}
Group Related Routes
// ✅ Good - Related routes in same controller
@Controller('/users')
class UserController {
@Get()
getUsers() { /* ... */ }
@Get(':id')
getUser() { /* ... */ }
@Get(':id/posts')
getUserPosts() { /* ... */ }
}
// ❌ Bad - Related routes scattered
@Controller('/users')
class UserController {
@Get()
getUsers() { /* ... */ }
}
@Controller('/posts')
class PostController {
@Get('user/:id')
getUserPosts() { /* ... */ }
}
Next Steps
- Add Middleware to your controllers for authentication
- Implement Request Validation in your controller methods
- Handle Exceptions properly in your controllers