The Stack

Billet is a server-rendered TypeScript starter built on Bun. Templates are JSX, compiled by Bun and rendered to HTML in the same process that answers the request—no client framework, no virtual DOM, no hydration step. Pages arrive as plain HTML, and a few hundred lines of signals bind interactivity to that markup where a page needs it.

How data flows

Every request follows the same three-step path. Services fetch or mutate data, controllers decide what to do with it, and templates turn it into HTML. There are no loading states because data is always resolved before the template renders.

services/        →  controllers/     →  templates/
  query the db        call services        receive props
  return typed        pick a template      render HTML
  data                pass data in         return Response

Project structure

src/
├── client/                  # Browser-side code
│   ├── main.ts              # Entry point: registers pages and components
│   ├── page-lifecycle.ts    # Per-page init and cleanup
│   ├── reactive/            # Signals, components, bind(), submit helper
│   ├── style.css            # Global styles (CSS entry point)
│   ├── components/          # Shared components + CSS (nav, todo list)
│   └── pages/               # Page JS & CSS (co-located)
│
├── server/                  # Server-side code (Bun)
│   ├── main.ts              # Server entry point
│   ├── routes/              # URL → controller mapping
│   ├── controllers/         # Request handlers
│   │   ├── app/             # View controllers (HTML)
│   │   ├── api/             # API controllers (JSON)
│   │   ├── auth/            # Auth flows
│   │   └── team/            # Team management (TEAMS_ENABLED)
│   ├── templates/           # Full-page JSX templates
│   ├── components/          # Reusable server JSX
│   ├── services/            # Business logic & data
│   ├── middleware/          # Auth, CSRF, rate limiting
│   ├── utils/               # Shared helpers
│   └── database/            # Migrations
│
└── shared/                  # The data-* vocabulary and header names both sides import

Feedback stack

Five layers of automated checks run before code reaches production.

LayerWhat it catches
TypeScript strict modeType errors, null safety violations, implicit any
Biome lintingCode quality, unused variables, console statements, unsafe patterns
Pre-commit hooksFormatting, lint errors, and type checks before every commit
Test suiteRegressions across services, controllers, templates, and client scripts
GitHub Actions CILint, build, and the full suite against a real PostgreSQL before a branch can merge; a scheduled audit flags vulnerable dependencies

Technology choices

A small, deliberate set of tools chosen for speed, simplicity, and minimal abstraction.

TechnologyRole
BunRuntime, bundler, test runner, and package manager
PostgreSQLPrimary data store with raw SQL via tagged templates
Bun CSS bundlerNative @import resolution, CSS nesting, and minification
SignalsAn in-repo reactive layer that binds to server markup by name—no virtual DOM, no eval, no full SPA
PreactThe JSX runtime, used only by renderToString() on the server—none of it reaches the browser
ResendTransactional email in production; a console provider takes its place in development