Routing
Balda provides flexible routing through direct server registration, the router singleton, or controller decorators.
Route Registration Methods
1. Direct Server Routes
const server = new Server({ port: 3000 });
server.get('/users', (req, res) => res.json({ users: [] }));
server.post('/users', (req, res) => res.created(req.body));
server.put('/users/:id', (req, res) => res.json({ id: req.params.id }));
server.delete('/users/:id', (req, res) => res.noContent());
2. Router Singleton
Useful for modular route definitions:
import { router } from 'balda';
router.get('/health', (req, res) => res.json({ status: 'ok' }));
router.post('/login', authMiddleware, (req, res) => res.json({ token: '...' }));
3. Controller Decorators
Recommended for organized, feature-based routing:
import { controller, get, post } from 'balda';
@controller('/users')
export class UsersController {
@get('/') getAll(req, res) { res.json({ users: [] }); }
@get('/:id') getById(req, res) { res.json({ id: req.params.id }); }
@post('/') create(req, res) { res.created(req.body); }
}
See Controllers for detailed controller documentation.
Route Parameters
Path Parameters
// Single parameter
server.get('/users/:id', (req, res) => {
res.json({ id: req.params.id });
});
// Multiple parameters
server.get('/users/:userId/posts/:postId', (req, res) => {
const { userId, postId } = req.params;
res.json({ userId, postId });
});
Query Parameters
server.get('/users', (req, res) => {
const { page = 1, limit = 10, search } = req.query;
res.json({ page: Number(page), limit: Number(limit), search });
});
Route Patterns
// Static route
server.get('/about', (req, res) => res.json({ message: 'About' }));
// Dynamic parameter
server.get('/users/:id', (req, res) => res.json({ id: req.params.id }));
// Optional parameter
server.get('/posts/:id?', (req, res) => {
req.params.id ? res.json({ id }) : res.json({ posts: [] });
});
// Wildcard
server.get('/files/*', (req, res) => {
res.json({ filePath: req.params['*'] });
});
Route Precedence
Routes are matched in registration order. Define specific routes before general ones:
// Specific first
server.get('/users/admin', (req, res) => res.json({ admin: true }));
// General after
server.get('/users/:id', (req, res) => res.json({ id: req.params.id }));
Type-Safe Routing
Type your request parameters and responses for better TypeScript support:
import { Request, Response } from 'balda';
// Type path parameters
server.get('/users/:id', (req: Request<{ id: string }>, res: Response) => {
const { id } = req.params; // ✅ Typed!
res.json({ id });
});
// Type response body
type UserResponse = { id: string; name: string; email: string };
server.get('/users/:id', (req: Request<{ id: string }>, res: Response<UserResponse>) => {
res.json({ id: req.params.id, name: "John", email: "john@example.com" });
});
// With validation - body/query passed as typed arguments
const CreateUserSchema = z.object({ name: z.string(), email: z.string() });
@post('/')
@validate.body(CreateUserSchema)
async create(
req: Request<{}>,
res: Response<UserResponse>,
body: z.infer<typeof CreateUserSchema> // ✅ Validated & typed
) {
res.created({ id: '123', ...body });
}
See Request-Response for more on type-safe requests.
Route Groups
Use server.group() to organize related routes:
server.group('/api/v1', (r) => {
r.get('/users', (req, res) => res.json({ users: [] }));
r.get('/posts', (req, res) => res.json({ posts: [] }));
});
// With middleware
server.group('/admin', [authMiddleware, adminMiddleware], (r) => {
r.get('/dashboard', (req, res) => res.json({ ok: true }));
});
See the Server documentation for more details on grouping and middleware.