Arachne builds static sites, server‑rendered apps and APIs from one TypeScript codebase, on Bun.

bunx @arachnejs/kit create my-app --template static
cd my-app && bun install
bun run dev

Version 0.1.1 is a pre-release: APIs can still change. Getting started walks through a first app.

Pages are JSX compiled to direct DOM operations. State lives in signals, so a change updates the text node or attribute that reads it: components run once, and there is no virtual DOM to diff. The route table, with its layouts, data loaders and head tags, runs on the server, at build time and in the browser.

On the server, one schema validates a request, types the handler, and produces the OpenAPI document and the typed client. Accounts, sessions, permissions, mail, file storage and migrations ship as packages of the framework, each usable on its own.

--template static
HTML for every route, rendered at build time. After the first load the page hydrates and routes on the client, fetching each page's data as a JSON file. Upload dist/ to any static host.
--template server
Pages rendered per request on a Bun server, plus API routes, sessions, accounts and permissions. The same route table runs on the server and in the browser.
--template api
No pages. Typed routes validated by schemas: JSON, forms and multipart bodies, file uploads, errors with issue paths, CORS, rate limits, and an OpenAPI 3.1 document.

A page with loader data, and a validated API route. Requests without a name get a 422 before the handler runs.

app/routes.tsx
import type { RouteDefinition, RouteProps } from "@arachnejs/router";
import { signal } from "@arachnejs/signals";

function Home(props: RouteProps) {
  const data = props.data as { now: string };
  const count = signal(0);
  return (
    <main>
      <h1>Hello, Arachne</h1>
      <p>Rendered at {data.now}.</p>
      <button type="button" onClick={() => count.set(count() + 1)}>
        Clicked {count()} times
      </button>
    </main>
  );
}

export const routes: RouteDefinition[] = [
  { path: "/", component: Home, head: { title: "Home" } },
];
app/server.ts
import { defineServer } from "@arachnejs/kit";
import { s } from "@arachnejs/schema";
import { route } from "@arachnejs/server";

const greet = route({
  method: "GET",
  path: "/api/greet",
  query: s.object({ name: s.string({ min: 1, max: 40 }) }),
  handler: (ctx) => ({ message: `Hello, ${ctx.query.name}!` }),
});

export default defineServer({
  routes: [greet],
  loaders: {
    "/": () => ({ now: new Date().toISOString() }),
  },
});

Pages

Server and data

UI

Foundation

On by default:

  • CSP with a per-request nonce, HSTS, nosniff, frame and referrer policies
  • Every declared input validated; failures answer 422 with the path of each issue
  • Parameterised SQL only; update and delete refuse to run without a where clause
  • argon2id passwords, hashed session and API tokens, __Host- cookies
  • CSRF checks via Origin and Fetch Metadata, login lockout, rate limits, audit events

Pre-release. Static, server and API apps work end to end and are covered by unit, process and browser tests. Not done yet: Postgres and MySQL drivers, OAuth and passkeys, streaming SSR, a GraphQL adapter. The progress log has the details.

Read nextGetting startedFramework guidekitrouterserverschemaauthui

arachne 0.1.12026-09-30 · d1aad1b68 pages