Skip to main content

Server

The Server class is the main entry point for creating Balda applications. It handles HTTP requests, manages middleware, and orchestrates the entire application lifecycle.

Creating a Server

import { Server } from "balda";

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: "*" },
bodyParser: { 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";

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

import { Server } from "balda";

const server = new Server({
port: 3000,
host: "localhost",
controllerPatterns: ["./src/controllers/**/*.ts"],
plugins: {
cors: { origin: "*" },
bodyParser: { json: { sizeLimit: "10mb" } },
},
swagger: true,
});
tip

See the Configuration Guide for detailed options and Plugins Overview for available plugins.

Server Methods

HTTP Methods

// Basic routes
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.patch("/users/:id", (req, res) => res.json(req.body));
server.delete("/users/:id", (req, res) => res.noContent());

// With middleware
server.post("/users", authMiddleware, (req, res) => res.created(req.body));
server.put("/users/:id", [authMiddleware, auditMiddleware], (req, res) => {
res.json({ id: req.params.id });
});

Middleware Management

// Single middleware
server.use((req, res, next) => {
console.log(`${req.method} ${req.url}`);
next();
});

// Multiple middleware
server.use(authMiddleware, loggingMiddleware);

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 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";

declare module "balda" {
interface Server {
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 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";

const server = new Server({
port: 3000,
controllerPatterns: ["./src/controllers/**/*.ts"],
plugins: {
cors: { origin: "*" },
bodyParser: { json: { sizeLimit: "10mb" } },
},
});

// Global middleware
server.use((req, res, next) => {
console.log(`${req.method} ${req.url}`);
next();
});

// Error handling
server.setErrorHandler((req, res, next, error) => {
res.internalServerError({ error: error.message });
});

// Health check
server.get("/health", (req, res) => {
res.json({ status: "ok" });
});

// Start server
server.listen(({ port }) => {
console.log(`Server running on http://localhost:${port}`);
});