A production-ready TypeScript monorepo. Ship fast, sleep well.
pnpm setup && docker compose up -d && pnpm db:migrate && pnpm devBackend runs on localhost:9999. Frontend on localhost:5173.
Backend — Hono, Drizzle, PostgreSQL, better-auth Frontend — React 19, TanStack Router, Tailwind v4 Extras — File uploads, WebSockets, background jobs, rate limiting
This is a GitHub template. Click Use this template → Create a new repository on GitHub, then clone your new repo.
You need Node 20+ and pnpm.
git clone https://github.com/<your-org>/<your-repo> my-app
cd my-app
pnpm setuppnpm setup installs dependencies and writes a .env file with a generated auth secret.
Start the database:
docker compose up -d # Starts PostgreSQL + Redis
pnpm db:migrate # Creates the initial tablesNote on auth tables: Better Auth manages its own tables (users, sessions, accounts). The initial migration already includes them. If you add Better Auth plugins later (2FA, API keys, organisations, etc.), regenerate the schema first:
npx @better-auth/cli generate # Updates packages/db/src/schema/ from your auth config pnpm db:generate # Creates the migration pnpm db:migrate # Applies itSee the Better Auth database docs for the full reference.
Run everything:
pnpm devOpen localhost:5173. You're live.
pnpm dev # Run everything
pnpm test # Run tests
pnpm lint # Check code
pnpm typecheck # Check typespnpm new:module postsThis scaffolds a complete module at apps/backend/src/modules/posts/:
| File | Purpose |
|---|---|
routes.ts |
OpenAPI route definitions with Zod schemas |
handlers.ts |
HTTP handlers — reads input, calls repo, maps Result to response |
posts.repository.ts |
Data access — uses tryInfra, returns Result, never throws |
posts.errors.ts |
Typed domain error variants (PostNotFound, etc.) |
usecases/ |
Pure business logic — no DB, no async, fully unit-testable |
__tests__/ |
Integration test stubs |
index.ts |
Wires routes to handlers, exports the router |
Register it in apps/backend/src/routes/index.ts:
import posts from "@/modules/posts";
// Authenticated routes:
export const routes = [users, posts];
// Or public routes:
export const publicRoutes = [health, posts];Then flesh out the repository with real Drizzle queries and add your DB schema to packages/db/src/schema/.
Create apps/frontend/src/routes/posts.tsx:
import { createFileRoute } from "@tanstack/react-router";
export const Route = createFileRoute("/posts")({
component: () => <div>Posts</div>,
});Done. TanStack Router handles the rest.
Edit packages/db/src/schema/ and run:
pnpm db:generate # Creates migration
pnpm db:migrate # Applies itimport { getUploadUrl, generateKey } from "@/lib/storage";
// Generate presigned upload URL
const key = generateKey("photo.jpg", "avatars");
const url = await getUploadUrl({ key, contentType: "image/jpeg" });
// Client uploads directly to S3/R2
await fetch(url, { method: "PUT", body: file });import { wsManager } from "@/lib/ws";
// Send to everyone in a room
wsManager.broadcast("notifications", { type: "new-message", data });
// Send to a specific user
wsManager.sendToUser(userId, { type: "alert", message: "Hey!" });import { addJob } from "@/jobs";
await addJob("email", {
to: "user@example.com",
template: "welcome",
data: { name: "Alex" },
});Run workers: pnpm --filter backend jobs
import { rateLimit, authRateLimit } from "@/lib/rate-limit";
// 100 requests per minute
app.use("/api/*", rateLimit());
// 5 attempts per 5 minutes (for login)
app.post("/api/auth/login", authRateLimit, loginHandler);Backend → Docker on any VPS, or Railway/Render Frontend → Vercel (zero config) Database → Supabase, Neon, or Railway
See docs/DEPLOYMENT.md for the full guide.
apps/backend/src/
modules/ ← Your features go here
lib/ ← Reusable utilities
jobs/ ← Background workers
middlewares/ ← Auth, etc.
apps/frontend/src/
routes/ ← Pages (file-based routing)
lib/ ← API client, helpers
components/ ← UI components
packages/
db/ ← Database schemas
shared/ ← Types shared everywhere
email-templates/ ← Email builders- Architecture Guide — How the backend is structured
- Backend Decisions — Why each backend choice was made
- Frontend Decisions — Why each frontend choice was made
- Batteries Included — All built-in utilities with usage examples
- Deployment Guide — Ship to production
- Writing Style Guide — How we write docs and articles
- API Docs — Auto-generated from your code
When you create a repo from this template, you own it. There is no upstream to pull from. Delete what you don't need, rename what makes sense to rename, and diverge freely.
What to keep: the packages/shared Result type, the tryInfra pattern, the module scaffolder, the Biome config.
What to replace: the example users module with your own domain, the license, this README.
Proprietary. Copyright © 2026 Orcta. All rights reserved.
This codebase is not open source. Do not distribute, sublicense, or use outside the organisation without written permission.