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.ts handles all HTTP traffic through a single fetch handler with configurable idleTimeout for long-running streams.
  • Bun.file in server/static.ts streams 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.ts and scripts/dev.ts manages development subprocesses and hot-reload orchestration.
  • Bun.SQL in server/db/client.ts provides 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →