Routing
Balda.js provides a flexible and intuitive routing system that supports both direct route registration and controller-based routing with decorators.
Route Registration
Direct Route Registration
You can register routes directly on the server instance:
import { Server } from 'balda-js';
const server = new Server({ port: 3000 });
// GET route
server.get('/users', (req, res) => {
res.json({ users: [] });
});
// POST route
server.post('/users', (req, res) => {
res.created(req.body);
});
// PUT route
server.put('/users/:id', (req, res) => {
res.json({ id: req.params.id, ...req.body });
});
// PATCH route
server.patch('/users/:id', (req, res) => {
res.json({ id: req.params.id, ...req.body });
});
// DELETE route
server.delete('/users/:id', (req, res) => {
res.noContent();
});
Router Singleton Registration
You can also register routes using the exported router singleton. This is useful when defining routes outside of a server instance (e.g., library-level or modular route definitions). Both forms are supported: with or without middleware, and middleware can be a single function or an array.
import { router } from 'balda-js';
// Handler-only
router.get('/users', (req, res) => {
res.json({ users: [] });
});
// With single middleware
router.get('/admin/users', authMiddleware, (req, res) => {
res.json({ users: [] });
});
// With multiple middleware
router.get('/secure/users', [authMiddleware, auditMiddleware], (req, res) => {
res.json({ users: [] });
});
// With Swagger/OpenAPI options (optional last argument)
router.get('/users', (req, res) => {
res.json({ users: [] });
}, {
summary: 'List users',
tags: ['Users']
});
Controller-Based Routing
Controllers provide a more organized way to define routes:
import { controller, get, post, put, del } from 'balda-js';
@controller('/users')
export class UsersController {
@get('/')
async getAllUsers(req, res) {
res.json({ users: [] });
}
@get('/:id')
async getUserById(req, res) {
res.json({ id: req.params.id });
}
@post('/')
async createUser(req, res) {
res.created(req.body);
}
@put('/:id')
async updateUser(req, res) {
res.json({ id: req.params.id, ...req.body });
}
@del('/:id')
async deleteUser(req, res) {
res.noContent();
}
}
Route Parameters
Path Parameters
Define dynamic segments in your routes:
@controller('/users')
export class UsersController {
@get('/:id')
async getUserById(req, res) {
const id = req.params.id;
res.json({ id });
}
@get('/:userId/posts/:postId')
async getUserPost(req, res) {
const { userId, postId } = req.params;
res.json({ userId, postId });
}
}
Query Parameters
Access query string parameters:
@get('/users')
async getUsers(req, res) {
const { page = 1, limit = 10, search } = req.query;
// Handle pagination and search
res.json({
page: parseInt(page),
limit: parseInt(limit),
search
});
}
Route Options
Middleware
Apply middleware to specific routes:
@get('/admin/users', { middleware: [authMiddleware, adminMiddleware] })
async getAdminUsers(req, res) {
res.json({ users: [] });
}
Swagger Documentation
Add OpenAPI documentation to routes:
@get('/', {
swagger: {
summary: 'Get all users',
description: 'Retrieve a list of all users with pagination',
tags: ['Users'],
responses: {
200: {
description: 'List of users',
content: {
'application/json': {
schema: {
type: 'array',
items: { $ref: '#/components/schemas/User' }
}
}
}
}
}
}
})
async getAllUsers(req, res) {
res.json({ users: [] });
}
Route Patterns
Static Routes
@get('/about')
async getAbout(req, res) {
res.json({ message: 'About page' });
}
Dynamic Routes
@get('/users/:id')
async getUser(req, res) {
const id = req.params.id;
res.json({ id });
}
Optional Parameters
@get('/posts/:id?')
async getPost(req, res) {
const id = req.params.id;
if (id) {
res.json({ id });
} else {
res.json({ posts: [] });
}
}
Wildcard Routes
@get('/files/*')
async getFile(req, res) {
const filePath = req.params['*'];
res.json({ filePath });
}
Route Precedence
Routes are matched in the order they are registered. More specific routes should be defined before more general ones:
@controller('/users')
export class UsersController {
// Specific route first
@get('/admin')
async getAdminUsers(req, res) {
res.json({ users: [] });
}
// General route after
@get('/:id')
async getUserById(req, res) {
res.json({ id: req.params.id });
}
}
Route Groups
Organize related routes using controllers:
@controller('/api/v1/users')
export class UsersController {
@get('/')
async getAllUsers(req, res) {
res.json({ users: [] });
}
}
@controller('/api/v1/posts')
export class PostsController {
@get('/')
async getAllPosts(req, res) {
res.json({ posts: [] });
}
}
Error Handling
404 Not Found
Routes that don't match any pattern return a 404 response:
// This will be handled by the 404 handler
server.get('/nonexistent', (req, res) => {
// This won't be reached
});
Custom 404 Handler
server.setErrorHandler((req, res, next, error) => {
if (error.name === 'RouteNotFoundError') {
return res.notFound({
error: 'Route not found',
path: req.url
});
}
next(error);
});
Route Testing
Mock Server
Use the mock server for testing routes:
import { Server } from 'balda-js';
const server = new Server({ port: 3000 });
server.get('/users', (req, res) => {
res.json({ users: [] });
});
// Get mock server for testing
const mockServer = await server.getMockServer();
// Test the route
const response = await mockServer.get('/users');
console.log(response.statusCode()); // 200
console.log(response.body()); // { users: [] }
Best Practices
1. Use Controllers for Organization
// Good: Organized by resource
@controller('/users')
export class UsersController {}
@controller('/posts')
export class PostsController {}
// Avoid: Mixed resources in one controller
@controller('/api')
export class ApiController {} // Too broad
2. Consistent Naming
// Good: RESTful naming
@get('/users') // GET /users
@get('/users/:id') // GET /users/123
@post('/users') // POST /users
@put('/users/:id') // PUT /users/123
@del('/users/:id') // DELETE /users/123
// Avoid: Inconsistent naming
@get('/getUsers')
@post('/createUser')
@put('/updateUser')
3. Parameter Validation
@get('/users/:id')
async getUserById(req, res) {
const id = parseInt(req.params.id);
if (isNaN(id)) {
return res.badRequest({ error: 'Invalid user ID' });
}
res.json({ id });
}
4. Query Parameter Handling
@get('/users')
async getUsers(req, res) {
const page = Math.max(1, parseInt(req.query.page) || 1);
const limit = Math.min(100, Math.max(1, parseInt(req.query.limit) || 10));
res.json({ page, limit });
}
5. Route Documentation
@get('/', {
swagger: {
summary: 'Get all users',
description: 'Retrieve a paginated list of users',
tags: ['Users'],
parameters: [
{
name: 'page',
in: 'query',
description: 'Page number',
schema: { type: 'integer', default: 1 }
}
]
}
})
async getAllUsers(req, res) {
res.json({ users: [] });
}
Balda.js routing provides a powerful and flexible system for defining your application's endpoints with support for both simple and complex routing patterns.