Skip to main content

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.