Documentation

HTTP Server

Frame-Master wraps Bun.serve() with a plugin-aware request pipeline.

Overview

Built on Bun: https://bun.sh/docs/api/http

Server Initialization

Startup steps:

  • Load frame-master.config.ts
  • Initialize plugins in order
  • Merge serverConfig from plugins with HTTPServer
  • Run serverStart hooks
  • (Dev) attach file watchers
  • Start Bun.serve() with merged config and fetch handler
export default async (params?: {
  config?: FrameMasterConfig;
  pluginLoader?: PluginLoader;
  builder?: Builder;
}) => {
  // Loads and initializes all core components of the server.
  await InitAll({ loders: params });
 
  Bun.serve({...});
};

Warning: Conflicting server options (e.g., ports) throw unless disableHttpServerOptionsConflictWarning is true.

Route Handling

Static Routes

const config: FrameMasterConfig = {
  HTTPServer: {
    port: 3000,
    routes: {
      "/api/health": () => Response.json({ status: "ok" }),
      "/api/version": () =>
        Response.json({ version: "1.0.0", framework: "Frame-Master" }),
    },
  },
};

Plugin Routes

const myPlugin: FrameMasterPlugin = {
  name: "my-api-plugin",
  version: "1.0.0",
  serverConfig: {
    routes: {
      "/api/custom": () => Response.json({ message: "From plugin" }),
    },
  },
  router: {
    request(master) {
      if (master.pathname === "/api/complex") {
        master.setResponse(JSON.stringify({ data: "Complex logic" }), {
          headers: { "Content-Type": "application/json" },
        });
      }
    },
  },
};

Route priority:

  1. Plugin routes (earlier plugins first)
  2. HTTPServer config routes
  3. masterRequest fallback via plugin request hooks

WebSocket Support

const config: FrameMasterConfig = {
  HTTPServer: {
    port: 3000,
    websocket: {
      maxPayloadLength: 16 * 1024 * 1024,
      idleTimeout: 120,
      backpressureLimit: 1024 * 1024,
      perMessageDeflate: true,
    },
  },
};

Plugin upgrade + handlers:

const wsPlugin: FrameMasterPlugin = {
  name: "websocket-plugin",
  version: "1.0.0",
  serverConfig: {
    routes: {
      "/ws/my-identifier": (req, server) =>
        server.upgrade(req as Request, { data: { id: "my-identifier" } }) ||
        Response.json({ error: "WebSocket upgrade failed" }, { status: 400 }),
    },
  },
  websocket: {
    onOpen(ws) {
      const { id } = ws.data as { id: string };
      ws.send(
        id === "my-identifier"
          ? "Welcome, my-identifier!"
          : "Welcome, unknown identifier!",
      );
    },
    onMessage(ws, message) {
      const { id } = ws.data as { id: string };
      if (id === "my-identifier") ws.send(`Echo: ${message}`);
    },
    onClose(ws) {
      const { id } = ws.data as { id: string };
      if (id === "my-identifier") console.log("WebSocket closed");
    },
  },
};

More: https://bun.sh/docs/api/websockets

Development Features

File System Watchers

absolutePath refers to the full path from the root of the project, while filePath is relative to the watched directory.

const myPlugin: FrameMasterPlugin = {
  name: "my-plugin",
  version: "1.0.0",
  fileSystemWatchDir: ["src/pages", "src/components"],
  onFileSystemChange(event, file, absolutePath) {
    console.log(`${event}: ${file}`);
    if (event === "change" && file.endsWith(".tsx"))
      rebuildComponent(absolutePath);
  },
};

Production: watchers are disabled when NODE_ENV=production.

Best Practices

  • Set development: true during local dev for better errors
  • Keep plugin request hooks fast; avoid heavy sync work
  • Place request-modifying plugins early, response-modifying plugins later
  • Use custom error handlers for better UX and security
  • Disable noisy logging in production

External Resources

Next Steps