Skip to main content

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.