Atlas: Architecture Overview#

Atlas is a collection of composable, functional Bun/TypeScript packages for building APIs, full-stack applications, and CLI tools. This document describes the architecture, package relationships, and design philosophy.

Dependency Graph#

@atlas/config (foundation — typed env loading)
  ├── @atlas/db
  ├── @atlas/server
  ├── @atlas/storage
  └── @atlas/cli

@atlas/migrate (depends on @atlas/db)
@atlas/auth (depends on @atlas/db, @atlas/server)
  └── @atlas/auth/social (subpath — OAuth-client side: Google/GitHub/Apple/MS/FB/X/TikTok)
@atlas/admin (depends on @atlas/db, @atlas/server)
@atlas/security (depends on @atlas/db, @atlas/auth)
@atlas/oauth (depends on @atlas/db, @atlas/server, @atlas/auth)
@atlas/sso (depends on @atlas/db, @atlas/server, @atlas/auth)
@atlas/edge (standalone — no sibling deps)
@atlas/email (standalone — no sibling deps)
@atlas/share (depends on @atlas/email — share URL builders + server-side share-by-email)

@atlas/cache (optional, can use @atlas/config)
@atlas/request (standalone — no sibling deps)
@atlas/mcp (depends on @atlas/db, @atlas/server, @atlas/config)
@atlas/ai (depends on @atlas/server for the withAi pipe; no external deps)

@atlas/ui (frontend, optional browser-side usage)

Packages are shallow — max 1 level of dependencies on siblings. This keeps them lightweight and composable.

Core Packages#

Config#

@atlas/config is the foundation. It provides typed environment variable loading that reads .env at startup and produces a frozen, immutable config object.

const config = defineConfig({
  database: env("DATABASE_URL"),
  port: env("PORT", { parse: Number, default: "3000" }),
})

All other packages optionally depend on config or accept config values directly. No forced wiring.

Database#

@atlas/db is the largest package, with three layers:

  1. Query Builder — Ecto-inspired fluent chains for SELECT, INSERT, UPDATE, DELETE, JOIN operations. Fully immutable, functional API.
  2. Drivers — Uniform interface to Postgres (via Bun.sql) and SQLite (via bun:sqlite) with transaction support.
  3. Schemas & Changesets — Type-safe table definitions and Zod-powered input validation.
const users = defineSchema("users", {
  id: column.serial().primaryKey(),
  email: column.text().unique(),
})

const query = from(users).where(q => q("email").equals("[email protected]"))
const result = await db.one(query)

Server#

@atlas/server wraps Bun.serve with a Plug-inspired pipe system. Requests flow through immutable Conn objects that pipes transform.

Sub-modules:

  • @atlas/server — Core HTTP routing with get(), post(), put(), patch(), del() route builders, plus the adapter pattern (createAdapter, compose) for running multiple listeners
  • @atlas/server/ws — WebSocket support
  • @atlas/server/sse — Server-Sent Events
const logger = pipe(c => { console.log(c.method); return c })
const auth = pipe(c => {
  const token = c.headers.get("authorization")
  return token ? assign(c, { userId: decode(token).id }) : halt(c, 401)
})

serve({
  routes: [
    get("/users", pipeline(logger, auth)(handler)),
  ],
})

Pipes are composable via pipeline(), which short-circuits on halt(). This approach is borrowed from Elixir and is more testable and readable than middleware.

Edge#

@atlas/edge is a TLS-terminating reverse proxy with built-in Let's Encrypt automation — the layer that would otherwise be Caddy or nginx in front of a Bun app.

  • ACME v2 client — ECDSA P-256 account, JWS-signed requests, HTTP-01 challenge solver, hand-rolled CSR via a small DER encoder. No external deps.
  • Reverse proxy — forwards requests with X-Real-IP, X-Forwarded-For (appended), X-Forwarded-Proto, X-Forwarded-Host. Hop-by-hop headers stripped. Host rewritten unless preserveHost: true.
  • Cert lifecycle — certs persist to a configurable store (filesystem default, in-memory for tests). A renewal timer reissues 30 days before expiry and server.reload({ tls }) swaps without dropping connections.
  • Localhost dev — when every site host matches isLocalHost, the edge runs plain HTTP on a non-privileged port. No certs, no sudo. The same edge.ts runs in dev and prod.
import { defineEdge, proxy } from "@atlas/edge"

defineEdge({
  acme: { email: "[email protected]", storage: "/var/atlas/edge" },
  sites: [{
    host: process.env.DOMAIN!,
    compress: ["gzip", "zstd"],
    routes: [{ handler: proxy("http://app:3000") }],
  }],
}).listen()

The templates/edge scaffold ships a complete deploy pattern: Procfile for local, Dockerfile + compose.yaml for production, with the cert volume and ACME staging-then-prod recipe pre-wired.

Auth#

@atlas/auth provides:

  • Primitiveshash(), verify(), token.sign(), token.verify() built on Bun's crypto
  • Flows — Prebuilt pipes like signup(), login(), requireAuth(), passwordReset() that work with @atlas/server and @atlas/db
  • Social login (@atlas/auth/social subpath) — pluggable OAuth-client for Google, GitHub, Apple, Microsoft, Facebook, X (Twitter), and TikTok. PKCE-S256 mandatory, state + verifier held in a signed HttpOnly cookie so the flow stays stateless. Your app owns the user table — onSuccess receives a normalized SocialProfile and you decide upsert/link.
const signupPipe = signup({
  db,
  table: "users",
  fields: ["email", "password"],
  onSuccess: (c, user) => json(c, 201, user),
})

// Social — same Conn/PipeFn shape:
const social = socialAuth({
  secret,
  providers: {
    google: google({ clientId, clientSecret, redirectUri }),
    github: github({ clientId, clientSecret, redirectUri }),
    tiktok: tiktok({ clientKey, clientSecret, redirectUri }),
    // …apple, microsoft, facebook, twitter
  },
})
get("/auth/google", social.start("google"))
get("/auth/google/callback", social.callback("google", {
  onSuccess: async (c, { profile }) => json(c, 200, { profile }),
}))

Security#

@atlas/security ships hardening primitives most apps end up writing themselves:

  • withSecurityHeaders() — strict default headers (HSTS, COOP/CORP, Permissions-Policy) and a CSP that defaults to 'self'-only with no inline script in production. Also stashes the real Bun socket peer onto req.peerIp so downstream rate-limit / audit code does not have to trust client-supplied X-Forwarded-For.
  • createDbRateLimit() / createMemoryRateLimit() — atomic UPSERT on Postgres, transactional read-modify-write on SQLite, in-memory for tests.
  • clientIp(req, { trustedProxies }) — only honors forwarded headers when the request actually arrived from a configured trusted proxy.
  • createSessionStore() — DB-backed, revocable JWT sessions with last_used_at tracking and a sweep helper. Pair with @atlas/auth#requireAuth.
  • generateSecret / verifyTotp / otpauthUrl / generateBackupCodes — pure node:crypto TOTP with backup codes.
  • decideInline() — safe-MIME allowlist for Content-Disposition: inline (never inlines SVG).
  • createAuditLogger() — fire-and-forget audit-event recorder; never throws, never blocks the response.

OAuth#

@atlas/oauth is an OAuth 2.1 server: PKCE-required authorization code, refresh-token rotation, device-code flow, RFC 8414 discovery, RFC 7009 revoke, and admin client management.

import { oauthRoutes } from "@atlas/oauth"
import { requireAuth } from "@atlas/auth"

serve({
  routes: [
    ...oauthRoutes({
      db,
      secret,
      scopes: ["read", "write"],
      loadUser,
      buildAccessTokenClaims,
      requireUser: requireAuth({ secret }),
      requireAdmin,
    }),
    ...appRoutes,
  ],
})

oauthRoutes(cfg) returns every endpoint as a flat Route[] ready to spread into serve. Mount only what you need by calling the per-flow factories (oauthAuthorizeRoutes, oauthTokenRoutes, …) directly. Sweeps for expired auth codes / refresh tokens / device codes are exported separately so you can run them on a schedule.

Email#

@atlas/email is a provider-agnostic transport plus a small HTML shell and two stock templates.

import { createEmailer, inviteEmail } from "@atlas/email"

const emailer = createEmailer({ apiKey: process.env.RESEND_API_KEY, from: process.env.RESEND_FROM })
await emailer.send({ to: "[email protected]", ...inviteEmail({ inviterName, product, signupUrl }) })

createEmailer returns the real Resend transport when both env vars are set and a console transport otherwise — dev environments stay unblocked without configuring a sending domain. send never throws; failures come back as { ok: false, error }. The included layout() is a 560px Outlook-friendly card; always pass user-supplied strings through escapeHtml.

Share#

@atlas/share is the smallest practical layer for "share this link" UX. Pure URL builders for the eight channels users actually use, plus a server-side shareEmail that dispatches through any @atlas/email transport.

import { share, shareUrl, shareEmail } from "@atlas/share"

shareUrl("twitter", { url, title, hashtags: ["atlas"] })
// → https://twitter.com/intent/tweet?url=…&text=…&hashtags=atlas

share({ url, title, text })
// → { twitter, facebook, linkedin, reddit, whatsapp, telegram, sms, email }

await shareEmail({
  emailer,
  to: "[email protected]",
  sharerName: "Wess",
  content: { url, title },
})

The URL builders have zero dependencies; shareEmail reuses the same layout() shell as inviteEmail/passwordResetEmail and escapes untrusted strings the same way.

Storage#

@atlas/storage provides S3-compatible object storage with:

  • upload() — PUT file to bucket
  • download() — GET file from bucket
  • presign() — Generate presigned URLs for direct client access
  • list() — List objects by prefix

Uses AWS Signature V4 (implemented from scratch, ~250 lines) with no external dependencies.

Cache#

@atlas/cache wraps Bun.redis with:

  • createCache() — Redis-backed cache
  • createMemoryCache() — In-memory (for testing)
  • cached() — Cache-aside pattern: fetch from cache, fallback to function, store result
  • invalidate() — Bust cache keys
const getUser = cached(cache, "user", async (id) => {
  return await db.one(from(users).where(q => q("id").equals(id)))
}, { ttl: 600 })

Request#

@atlas/request is an HTTP client built on fetch with:

  • request() — One-off requests
  • createClient() — Preconfigured clients with base URL, headers, retries
  • Providers — Drop-in configs for GitHub, Stripe, OpenAI, Resend (import from @atlas/request/providers)
  • Retry logic with exponential backoff
  • Request/response interceptors
import { github } from "@atlas/request/providers"

const gh = github({ token: process.env.GITHUB_TOKEN! })
const repos = await (await gh.get("/user/repos")).json()

CLI#

@atlas/cli provides:

  • Command parser — Define commands, flags, subcommands; parse argv
  • Foreman — Procfile parser and concurrent process runner with colored output
  • Built-in commandsatlas init (scaffold project), atlas add (add packages), atlas dev (start dev server), atlas mcp (launch MCP server)
cli("myapp", [
  command("serve", {
    flags: { port: flag("p", { type: "number", default: 3000 }) },
    run: ({ flags }) => startServer(flags.port),
  }),
])

await foreman({ web: "bun run server.ts", worker: "bun run worker.ts" })

Migrate#

@atlas/migrate manages timestamped SQL migrations with up/down support.

migrate.create("./migrations", "add_users")
await migrate.up(db, "./migrations")
await migrate.status(db, "./migrations")

Tracks applied migrations in schema_migrations table. Works with Postgres and SQLite.

UI#

@atlas/ui is a modular React + Mantine package with independent blocks:

  • @atlas/ui/provider — Theme and layout shell
  • @atlas/ui/forms — Form primitives and helpers
  • @atlas/ui/table — Data tables with sort/filter/pagination
  • @atlas/ui/auth — Login, signup, password reset pages
  • @atlas/ui/storage — File upload and image preview
  • @atlas/ui/nav — Navigation components
  • @atlas/ui/cache — Cache inspector and status badge
  • @atlas/ui/ai — Chat panel, message list, streaming display

Each block is independently importable and tree-shakeable.

Admin#

@atlas/admin auto-generates a Django-style admin panel from @atlas/db schemas. Provides:

  • API routes — CRUD endpoints for each model
  • Admin SPA — React UI with list, detail, create views, search, filters, bulk actions, custom actions, query builder
  • Metadata routes — Schema introspection, field info, relation discovery
const panel = admin({
  db,
  models: [
    model({
      schema: users,
      searchFields: ["email", "name"],
      filterFields: ["status"],
      bulkActions: ["delete", "export"],
    }),
  ],
})

serve({ routes: panel.mount([]) })

Serves SPA at /admin with full CRUD UI.

MCP#

@atlas/mcp provides a Model Context Protocol server for AI/LLM debugging and introspection. It exposes your app's database, routes, config, and logs to AI agents through the standard MCP protocol.

import { collectTools, createContext, createMcpServer } from "@atlas/mcp"

const ctx = createContext({ db, routes: myRoutes, config })
const mcp = createMcpServer(collectTools(ctx), ctx)

mcp.start()

Launch via CLI: atlas mcp. AI agents can then query your database, inspect routes, view config, and read logs interactively.

AI#

@atlas/ai provides a unified interface for AI/LLM operations with zero external dependencies — it calls provider REST APIs directly via fetch.

  • ProviderscreateProvider({ provider, key }) returns an AiProvider for "openai", "anthropic", or "ollama"
  • Chatprovider.chat(opts) for completions, provider.chatStream(opts) for streaming StreamChunks
  • ConversationscreateConversation/send for immutable message-history tracking
  • Embeddingsembed(provider, inputs) plus an in-memory createVectorStore()
  • RAGindex(rag, id, text) + query(rag, question) over a { ai, store, topK? } bag
  • AgentsrunAgent({ ai, system?, tools, maxIterations? }, prompt) for tool-using loops
  • Server pipewithAi(provider) attaches the provider to conn.assigns.ai
import { createProvider, send, createConversation, runAgent, tool } from "@atlas/ai"

const openai = createProvider({ provider: "openai", key: process.env.OPENAI_API_KEY! })

const conv = createConversation("You are a helpful assistant")
const { response } = await send(openai, conv, "Hello")

for await (const chunk of openai.chatStream({ messages: [{ role: "user", content: "Hi" }] })) {
  if (chunk.type === "text") process.stdout.write(chunk.content ?? "")
}

Works with OpenAI, Anthropic, Ollama, or any OpenAI-compatible endpoint (baseUrl override).

Templates#

Atlas ships with 10 project templates, scaffolded via atlas init --template <name>:

TemplateDescriptionKey Packages
minimalJust server + configconfig, server
apiREST API with db, auth, migrationsconfig, db, migrate, server, auth
edgeApp + TLS-terminating edge (replaces Caddy/nginx)config, server, edge
fullstackAPI + React frontendconfig, db, server, auth, ui
adminAPI + admin panelconfig, db, server, auth, admin
workerBackground job processorconfig, db, cache, cli
realtimeWebSocket + SSEconfig, server
socialnetworkUsers, posts, follows, likes, feeds, media, real-timeconfig, db, server, auth, cache, storage
cmsHeadless CMS with content types, publishing, webhooksconfig, db, server, auth, storage, admin
aiChatbot, RAG, agents, embeddings, streamingconfig, server, ai

Composition Patterns#

Typical Backend App#

// 1. Config
const config = defineConfig({ database: env("DATABASE_URL"), ... })

// 2. Database
const db = connect({ driver: "postgres", url: config.database })

// 3. Migrations
await migrate.up(db)

// 4. HTTP with pipes
serve({
  routes: [
    post("/auth/signup", signup({ db, table: "users", ... })),
    post("/auth/login", login({ db, table: "users", ... })),
    get("/api/users", pipeline(requireAuth({ secret }))(listUsers)),
    post("/api/files", pipeline(requireAuth({ secret }), parseMultipart)(uploadFile)),
  ],
})

Full Stack#

Use @atlas/ui blocks on the client:

// server.ts
import { get, halt, pipe, putHeader, serve } from "@atlas/server"

const page = await Bun.file("./admin.html").text() // HTML file that loads the React SPA
const spa = pipe((c) => putHeader(halt(c, 200, page), "content-type", "text/html; charset=utf-8"))

serve({
  routes: [
    get("/admin", spa),
    get("/admin/*", spa),
    ...apiRoutes,
  ],
})
<!-- admin.html -->
<html>
  <body>
    <div id="root"></div>
    <script type="module" src="./admin.tsx"></script>
  </body>
</html>
// admin.tsx
import { AdminApp } from "@atlas/admin"
import { createRoot } from "react-dom/client"

const root = createRoot(document.getElementById("root")!)
root.render(<AdminApp />)

AGENTS.md Convention#

Each package includes an AGENTS.md at its root — a compact manifest (~80 lines) of the public API.

Structure:

  • Exports — Each public function/type with signature and return type
  • Types — Data shapes that users need to generate correct code
  • Usage — Minimal working example
  • Dependencies — Sibling and external packages

Why this matters:

LLMs can read packages/db/AGENTS.md instead of traversing 20+ source files. This is critical for token efficiency in prompt engineering and code generation workflows.

Conventions:

  • Keep under 100 lines
  • Every public export must be listed
  • Include return types and argument types
  • Update whenever the API changes
  • Treat as part of the build process

Design Philosophy#

  1. Functional — No classes, immutable data, composition over inheritance
  2. Minimal dependencies — Wrap Bun's native APIs, don't reach for external packages
  3. Composable — Each package works independently or with others
  4. AI/LLM-friendly — Clear APIs, good types, predictable patterns, AGENTS.md reference
  5. No framework lock-in — Use what you need, combine with anything else
  6. Bun-native — Idiomatic to Bun's philosophy and APIs
  7. Shallow dependencies — Max 1 level of sibling package dependencies

External Dependencies#

PackageDeps
confignone
dbzod
migratenone
servernone
authnone
securitynone
oauthnone
edgenone
emailnone
sharenone
storagenone
cachenone
requestnone
ssonone
clinone
uireact, @mantine/*, @tanstack/*, lucide-react
adminreact, @mantine/*, @tanstack/*, lucide-react
mcpnone
ainone

Total: Only zod on the backend. Frontend uses React + Mantine + TanStack.

Development Environment#

  • Monorepo — Bun workspaces
  • Testingbun test across all packages
  • TypeScript — Strict mode
  • Linting — Biome (run bun run check / bun run tidy)
  • File naming — All lowercase, no dashes or underscores, subdirectories for organization
  • Environment.env in root (Postgres on localhost, SQLite in memory for tests)
Canonical sourcedocs/overview.md
Type to search guides and package references.