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,
});
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
| 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";
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}`);
});