How Instatic Uses Bun as Its JavaScript Runtime: Core Architecture Explained
TLDR: Instatic leverages Bun's native APIs—including Bun.serve, Bun.file, and Bun.Worker—to handle HTTP requests, stream static assets, and isolate plugin processes without any Node.js dependencies or polyfills.
The CoreBunch/Instatic repository is a self-hosted CMS built exclusively on Bun, a fast JavaScript runtime that replaces Node.js for both development and production. By utilizing Bun's built-in APIs for server management, file I/O, compression, and process spawning, Instatic achieves a single-binary deployment model that serves both the CMS API and React admin interface with minimal overhead.
HTTP Server Architecture with Bun.serve
At the heart of Instatic's runtime is server/index.ts, which initializes the application using Bun.serve. This native API replaces traditional Node.js HTTP servers like Express or Fastify, handling every incoming request—including CORS pre-flight and error handling—within a single process.
The server configuration sets idleTimeout: 0 specifically to support long-running AI streams without disconnections. The fetch callback stamps client IPs using server.requestIP(req) for rate-limiting logic, applies security headers, and routes requests through handleServerRequest.
// server/index.ts – starting the Bun server
import { createDbClient } from './db';
import { runMigrations } from './db/runMigrations';
import { handleServerRequest } from './router';
import { applySecurityHeaders } from './securityHeaders';
import { configurePublicOrigins, stampSocketIp } from './auth/security';
const config = readServerConfig();
configurePublicOrigins(config.publicOrigins);
const { db, migrations } = createDbClient(config.databaseUrl);
await runMigrations(db, migrations);
await syncSystemRoles(db);
Bun.serve({
port: config.port,
idleTimeout: 0, // no timeout for long‑running AI streams
async fetch(req, server) {
// CORS handling
const cors = corsHeaders(req.headers.get('origin'));
// IP stamping for rate‑limit logic
stampSocketIp(req, server.requestIP(req)?.address ?? null);
try {
const res = await handleServerRequest(req, { db, staticDir: config.staticDir });
for (const [k, v] of Object.entries(cors)) res.headers.set(k, v);
return applySecurityHeaders(res, new URL(req.url).pathname);
} catch (e) {
console.error('[server] Unhandled request error:', e);
return applySecurityHeaders(
new Response(JSON.stringify({ error: 'Internal server error' }), {
status: 500,
headers: { 'Content-Type': 'application/json', ...cors },
}),
new URL(req.url).pathname,
);
}
},
});
console.log(`[server] Listening on http://localhost:${config.port}`);
Request Routing and Security Middleware
Before requests reach the router, stampSocketIp extracts the client address from Bun's native socket information, while applySecurityHeaders injects security policies into the response. This chain runs entirely within Bun's runtime, avoiding external middleware dependencies.
Static File Handling and Compression
Instatic serves static assets—CSS, JavaScript, images, and the admin HTML shell—using Bun.file in server/static.ts. This API provides a File object that can be streamed directly or read into memory via await file.arrayBuffer(), eliminating the need for separate static-file server middleware.
Optimized Delivery with Native Compression
Text responses and static assets undergo compression using Bun.gzipSync and Brotli (brotliCompressSync). The serveStaticFile function checks the Accept-Encoding header and returns compressed payloads with proper Content-Encoding, Content-Type, and Cache-Control headers.
// server/static.ts – serving a static file with compression
import { resolve } from 'node:path';
import { BrotliCompressSync, constants as zlibConstants } from 'node:zlib';
export async function serveStaticFile(
staticDir: string,
pathname: string,
req?: Request,
): Promise<Response | null> {
const filePath = resolveStaticPath(staticDir, pathname);
if (!filePath) return null;
const file = Bun.file(filePath);
if (!(await file.exists())) return null;
const bytes = new Uint8Array(await file.arrayBuffer());
const acceptEncoding = req?.headers.get('accept-encoding') ?? null;
const encoding = selectEncoding(acceptEncoding);
if (encoding === 'br') {
const compressed = BrotliCompressSync(bytes, {
params: { [zlibConstants.BROTLI_PARAM_QUALITY]: 5 },
});
return new Response(new Uint8Array(compressed), {
headers: { 'content-encoding': 'br', 'content-type': contentType(filePath) },
});
}
return new Response(bytes, { headers: { 'content-type': contentType(filePath) } });
}
Worker Isolation for CPU-Intensive Tasks
To preserve the main server's responsiveness, Instatic offloads CPU-heavy operations—such as image-variant processing and plugin sandboxing—to isolated Bun.Worker instances. The server/plugins/host/workerPool.ts module manages these worker lifecycles, spawning QuickJS WASM sandboxes with limited permissions.
// server/plugins/host/workerPool.ts – launching a sandboxed plugin worker
import { Worker } from 'bun';
export function ensureWorkerFor(pluginId: string, entrypoint: string): Worker {
const worker = new Worker(entrypoint, {
// Workers run in a QuickJS WASM sandbox with limited permissions
env: { PLUGIN_ID: pluginId },
stdio: ['pipe', 'pipe', 'pipe'],
});
// Attach message handlers, timeouts, etc.
return worker;
}
Development Workflow and Process Orchestration
Local development relies on helper scripts that use Bun.spawn to manage subprocesses. The scripts/start.ts file launches the server process, while scripts/dev.ts orchestrates Docker checks and hot-reload functionality. This approach eliminates the need for external process managers like PM2 or Nodemon during development.
Database Connectivity with Bun.SQL
The database client abstraction in server/db/client.ts utilizes Bun.SQL to connect to both PostgreSQL and SQLite backends. This native database driver integrates seamlessly with the request lifecycle, providing async query capabilities without blocking the main thread.
Summary
- Bun.serve in
server/index.tshandles all HTTP traffic through a singlefetchhandler with configurableidleTimeoutfor long-running streams. - Bun.file in
server/static.tsstreams static assets directly, with responses compressed using native Bun.gzipSync and Brotli implementations. - Bun.Worker isolates CPU-intensive plugin operations via
server/plugins/host/workerPool.ts, preventing event loop blocking. - Bun.spawn in
scripts/start.tsandscripts/dev.tsmanages development subprocesses and hot-reload orchestration. - Bun.SQL in
server/db/client.tsprovides native database connectivity without external driver dependencies.
Frequently Asked Questions
Does Instatic require Node.js for any part of its runtime?
No. Instatic is built exclusively for the Bun JavaScript runtime. According to the CoreBunch/Instatic source code, the application uses Bun-native APIs like Bun.serve, Bun.file, and Bun.Worker throughout the codebase, eliminating the need for Node.js polyfills or compatibility layers.
How does Instatic handle static file serving without Express or Fastify?
Instatic serves static files directly through Bun.file as implemented in server/static.ts. This API returns a File object that can be streamed to the client or read into memory via await file.arrayBuffer(), eliminating the need for external static-file middleware while supporting automatic compression.
What is the purpose of Bun.Worker in Instatic?
Bun.Worker isolates CPU-intensive tasks such as image-variant processing and plugin sandboxing. The server/plugins/host/workerPool.ts module manages these workers to prevent long-running operations from blocking the main HTTP server's event loop, utilizing QuickJS WASM sandboxes for security.
How does the development server handle hot reloading?
The scripts/dev.ts file uses Bun.spawn to orchestrate subprocesses, enabling hot reload functionality during local development. This allows the server to restart automatically when code changes are detected without requiring external process managers or file watchers.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →