Skip to main content

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

ValueDescription
'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}`);
});
info

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
}
});
tip

For local development, you can generate self-signed certificates using tools like mkcert.

note

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.