Server
The Server class is the main entry point for creating Balda.js applications. It handles HTTP requests, manages middleware, and orchestrates the entire application lifecycle.
Creating a Server
import { Server } from 'balda-js';
const server = new Server({
port: 3000,
host: 'localhost'
});
Server Lifecycle
1. Initialization
When you create a server instance, it:
- Sets up the runtime connector (Node.js, Bun, or Deno)
- Initializes the router
- Applies global middleware
- Sets up the body parser
const server = new Server({
port: 3000,
plugins: {
cors: { origin: '*' },
json: { sizeLimit: '1mb' }
}
});
2. Route Registration
Routes can be registered in two ways:
Direct registration:
server.get('/users', (req, res) => {
res.json({ users: [] });
});
Controller-based registration:
@controller('/users')
export class UsersController {
@get('/')
async getUsers(req, res) {
res.json({ users: [] });
}
}
Using the global router:
For library-level or modular route definitions, you can import the shared router and register routes without a Server instance. These routes will be consumed by the server at startup.
import { router } from 'balda-js';
router.get('/health', (req, res) => {
res.json({ status: 'ok' });
});
router.post('/login', authMiddleware, (req, res) => {
res.json({ token: '...' });
});
router.get('/admin', [authMiddleware, adminMiddleware], (req, res) => {
res.json({ ok: true });
});
3. Server Startup
When you call server.listen(), the server:
- Imports controllers based on patterns
- Registers all routes
- Applies plugins
- Starts listening for requests
server.listen(({ port, host }) => {
console.log(`Server running on http://${host}:${port}`);
});
Server Configuration
Basic Options
import path from 'path';
const server = new Server({
port: 3000, // Server port
host: 'localhost', // Server host
controllerPatterns: [ // Controller file patterns, we use the import.meta.dirname to get the directory of the current file since it should work on both in a build and dev environment
path.join(import.meta.dirname, 'controllers/**/*.ts')
],
plugins: {}, // Plugin configuration
swagger: true, // Enable Swagger docs
tapOptions: {} // Runtime-specific options
});
Advanced Configuration
import path from 'path';
const server = new Server({
port: process.env.PORT || 3000,
host: process.env.HOST || '0.0.0.0',
controllerPatterns: [
path.join(import.meta.dirname, 'controllers/**/*.ts'),
path.join(import.meta.dirname, 'api/**/*.ts')
],
plugins: {
cors: { origin: '*' },
json: { sizeLimit: '10mb' },
static: { source: './public', path: '/public' }
},
swagger: {
type: 'standard',
models: {
User: {
type: 'object',
properties: {
id: { type: 'number' },
name: { type: 'string' }
}
}
}
}
});
Server Methods
HTTP Methods
// GET requests
// Handler-only
server.get('/users', (req, res) => {
res.json({ users: [] });
});
// POST requests
// With single middleware
server.post('/users', authMiddleware, (req, res) => {
res.created({ id: 1, name: 'John' });
});
// PUT requests
// With multiple middleware
server.put('/users/:id', [authMiddleware, auditMiddleware], (req, res) => {
res.json({ id: req.params.id, name: 'Updated' });
});
// PATCH requests
server.patch('/users/:id', (req, res) => {
res.json({ id: req.params.id, ...req.body });
});
// DELETE requests
server.delete('/users/:id', (req, res) => {
res.noContent();
});
Middleware Management
// Add global middleware
server.use((req, res, next) => {
console.log(`${req.method} ${req.url}`);
next();
});
// Add multiple middleware
server.use(
(req, res, next) => {
req.startTime = Date.now();
next();
},
(req, res, next) => {
res.on('finish', () => {
const duration = Date.now() - req.startTime;
console.log(`${req.method} ${req.url} - ${duration}ms`);
});
next();
}
);
Path Normalization and Groups
Paths are normalized to avoid duplicate or trailing slashes. Grouping allows composing a base path and shared middleware:
server.group('/api', (r) => {
r.get('/status', (req, res) => res.json({ ok: true })); // => /api/status
});
server.group('/v1', authMiddleware, (r) => {
r.get('/me', (req, res) => res.json({ me: '...' })); // => /v1/me with auth
});
server.group('/admin', [authMiddleware, adminMiddleware], (r) => {
r.get('/dashboard', (req, res) => res.json({ ok: true })); // => /admin/dashboard with both
});
Both the server instance and the global router support the same overloads:
- handler-only:
get(path, handler, swagger?) - single middleware:
get(path, middleware, handler, swagger?) - multiple middleware:
get(path, [mw1, mw2], handler, swagger?)
Error Handling
server.setErrorHandler((req, res, next, error) => {
console.error('Error:', error);
if (error.name === 'ValidationError') {
return res.badRequest({ error: error.message });
}
res.internalServerError({ error: 'Internal server error' });
});
Custom Not Found Handler
By default, Balda.js returns a standardized 404 error response when a route is not found. You can customize this behavior using setNotFoundHandler:
server.setNotFoundHandler((req, res) => {
res.status(404).json({
error: 'Not Found',
message: `The route ${req.url} does not exist`,
timestamp: new Date().toISOString()
});
});
Advanced Examples:
HTML Response for Web Applications:
server.setNotFoundHandler((req, res) => {
res.status(404).html(`
<!DOCTYPE html>
<html>
<head><title>404 - Page Not Found</title></head>
<body>
<h1>Oops! Page not found</h1>
<p>The page you're looking for doesn't exist.</p>
<a href="/">Go back home</a>
</body>
</html>
`);
});
Resetting to Default Behavior:
To reset to the default not found handler, pass undefined:
server.setNotFoundHandler(undefined);
Server Properties
Read-only Properties
// Get server URL
console.log(server.url); // http://localhost:3000
// Get server port
console.log(server.port); // 3000
// Get server host
console.log(server.host); // localhost
// Check if server is listening
console.log(server.isListening); // true/false
Utility Methods
import type { NodeHttpClient } from "balda-js";
declare module "balda-js" {
interface Server<H extends NodeHttpClient> {
db: typeof db;
}
}
// Get temporary directory
const tmpDir = server.tmpDir('uploads', 'images');
// Embed data for later use
server.embed('config', { apiKey: 'secret' });
// Get embedded data
const config = server.embed('config');
// Exit the server
server.exit(0);
Event Handling
// Handle graceful shutdown
server.on('SIGTERM', () => {
console.log('Received SIGTERM, shutting down gracefully');
server.close();
});
server.on('SIGINT', () => {
console.log('Received SIGINT, shutting down gracefully');
server.close();
});
Server Shutdown
// Graceful shutdown
async function shutdown() {
console.log('Shutting down server...');
await server.close();
console.log('Server shut down successfully');
process.exit(0);
}
// Handle shutdown signals
process.on('SIGTERM', shutdown);
process.on('SIGINT', shutdown);
Runtime Support
Balda.js automatically detects and adapts to your runtime:
Node.js
// Uses Node.js http module
const server = new Server({
port: 3000
});
Bun
// Uses Bun's HTTP server
const server = new Server({
port: 3000
});
Deno
// Uses Deno's HTTP server
const server = new Server({
port: 3000
});
Node.js HTTP Client Options
When running on Node.js, you can specify which HTTP module to use via the nodeHttpClient option:
const server = new Server({
port: 3000,
nodeHttpClient: 'http' // 'http' | 'http2' | 'https' | 'http2-secure'
});
Available Options
| Value | Description |
|---|---|
'http' | Standard HTTP/1.1 server (default) |
'http2' | HTTP/2 server without TLS (cleartext, h2c) |
'https' | HTTPS server with TLS encryption (HTTP/1.1 over TLS) |
'http2-secure' | HTTP/2 server with TLS encryption (recommended for production HTTP/2) |
HTTPS Configuration
When using nodeHttpClient: 'https', you must provide the httpsOptions with your TLS certificate and key:
import fs from 'fs';
const server = new Server({
port: 443,
nodeHttpClient: 'https',
httpsOptions: {
key: fs.readFileSync('./certs/private-key.pem'),
cert: fs.readFileSync('./certs/certificate.pem'),
}
});
server.listen(({ url }) => {
console.log(`🔒 Secure server running at ${url}`);
});
HTTP/2 Secure Configuration
For HTTP/2 with TLS (the standard way browsers connect via HTTP/2), use nodeHttpClient: 'http2-secure':
import fs from 'fs';
const server = new Server({
port: 443,
nodeHttpClient: 'http2-secure',
httpsOptions: {
key: fs.readFileSync('./certs/private-key.pem'),
cert: fs.readFileSync('./certs/certificate.pem'),
}
});
server.listen(({ url }) => {
console.log(`🚀 HTTP/2 secure server running at ${url}`);
});
Most browsers only support HTTP/2 over TLS, so 'http2-secure' is the recommended option for production HTTP/2 servers. The 'http2' option (cleartext h2c) is primarily useful for internal services or testing.
TLS Options
Both 'https' and 'http2-secure' require httpsOptions. You can pass any valid Node.js TLS options:
const server = new Server({
port: 443,
nodeHttpClient: 'http2-secure',
httpsOptions: {
key: fs.readFileSync('./certs/private-key.pem'),
cert: fs.readFileSync('./certs/certificate.pem'),
ca: fs.readFileSync('./certs/ca-certificate.pem'), // Certificate Authority
passphrase: 'your-passphrase', // If key is encrypted
minVersion: 'TLSv1.2', // Minimum TLS version
}
});
For local development, you can generate self-signed certificates using tools like mkcert.
The httpsOptions property is only available when nodeHttpClient is set to 'https' or 'http2-secure'. TypeScript will enforce this at compile time.
Complete Example
import { Server } from 'balda-js';
const server = new Server({
port: 3000,
host: 'localhost',
controllerPatterns: ['./src/controllers/**/*.ts'],
plugins: {
cors: { origin: '*' },
json: { sizeLimit: '10mb' },
log: { logRequest: true }
}
});
// Global middleware
server.use((req, res, next) => {
req.startTime = Date.now();
next();
});
// Error handler
server.setErrorHandler((req, res, next, error) => {
console.error('Error:', error);
res.internalServerError({ error: 'Something went wrong' });
});
// Custom not found handler
server.setNotFoundHandler((req, res) => {
console.warn(`404 Not Found: ${req.method} ${req.url}`);
res.status(404).json({
error: 'Not Found',
message: `Route ${req.url} does not exist`,
timestamp: new Date().toISOString()
});
});
// Direct route registration
server.get('/health', (req, res) => {
res.json({ status: 'ok', uptime: process.uptime() });
});
// Start server
server.listen(({ port, host }) => {
console.log(`🚀 Server running on http://${host}:${port}`);
console.log(`📚 API Documentation: http://${host}:${port}/docs`);
});
// Graceful shutdown
process.on('SIGTERM', () => server.close());
process.on('SIGINT', () => server.close());
The Server class provides a solid foundation for building scalable web applications with Balda.js.