Show HN: A monorepo where AI agents can safely build and maintain applications
A production-oriented monorepo starter built with pnpm, Turborepo, Cloudflare Workers, Hono, React, Tailwind CSS v4, and TanStack Router/Query. Designed for AI agents to safely build and maintain applications with clear conventions and boundaries.
Uh oh!
There was an error while loading. Please reload this page.
Notifications You must be signed in to change notification settings
Fork 0
Star 4
BranchesTags
Open more actions menu
Folders and files
NameName
Last commit message
Last commit date
Latest commit
History
47 Commits
47 Commits
.agents/skills
.agents/skills
.claude
.claude
.cursor
.cursor
.github
.github
.husky
.husky
.vscode
.vscode
apps
apps
assets
assets
hooks
hooks
make
make
packages
packages
.cursorignore
.cursorignore
.cursorindexingignore
.cursorindexingignore
.editorconfig
.editorconfig
.gitignore
.gitignore
.mcp.json
.mcp.json
.npmrc
.npmrc
.nvmrc
.nvmrc
.oxfmtrc.json
.oxfmtrc.json
.oxlintrc.json
.oxlintrc.json
.worktreeinclude
.worktreeinclude
AGENTS.md
AGENTS.md
CLAUDE.md
CLAUDE.md
LICENSE
LICENSE
Makefile
Makefile
README.md
README.md
package.json
package.json
pnpm-lock.yaml
pnpm-lock.yaml
pnpm-workspace.yaml
pnpm-workspace.yaml
skills-lock.json
skills-lock.json
tsconfig.json
tsconfig.json
turbo.json
turbo.json
Repository files navigation
A minimal, production-oriented monorepo starter built on pnpm workspaces with Turborepo, Cloudflare Workers, Hono, React (Vite), Tailwind CSS v4, and TanStack Router/Query. AI-ready, designed for edge deployment, and structured for production projects that scale.
Architecture Overview
Monorepo Structure
Starter apps today (worker-api, front-app). Prefixes below describe how the repo grows.
monorepo/
├── apps/ # Workers and frontends
│ ├── worker-api/ # REST API gateway
│ └── front-app/ # React SPA (Vite + TanStack)
├── packages/ # Shared @repo/* packages
│ ├── dtos-common/ # Zod wire contracts (api / rpc / queue / webhook)
│ ├── enums-common/ # Shared constrained string values (as const)
│ └── typescript-config/ # TypeScript configuration presets
├── make/ # Shared Makefile fragments (see make/README.md)
├── hooks/ # AI agent hooks (not Husky - see hooks/README.md)
├── package.json # Root package configuration
├── pnpm-workspace.yaml # Workspace configuration
├── turbo.json # Turborepo configuration
└── tsconfig.json # Root TypeScript configuration
Architecture Components
The monorepo is organized into two main categories: Backend Services and Frontend Applications, plus Shared Packages for common functionality.
flowchart TB subgraph entry [Public entry] direction LR Front["front-* :517x"] Ext["External providers"] McpClients["MCP clients"] end
subgraph publicWorkers [Public Workers] direction LR Gateway["worker-api :8700"] Webhook["webhook-* :876x"] Mcp["mcp-* :878x"] end
subgraph privateWorkers [Private Workers] direction LR Biz["worker-* (RPC only)"] Queue["queue-*"] end
subgraph shared [Shared packages] direction LR Enums["@repo/enums-common"] DTOs["@repo/dtos-common"] Enums --> DTOs end
Front --> Gateway Ext --> Webhook McpClients --> Mcp
Gateway --> Biz Webhook --> Biz Mcp --> Biz
Gateway --> Queue Webhook --> Queue Biz --> Queue
Front -.-> shared publicWorkers -.-> shared privateWorkers -.-> shared
Loading
Backend Services
Cloudflare Workers are organized by runtime role:
worker-api - Public HTTP gateway (Hono): CORS, validation, routing; coordinates internal Workers via RPC.
worker-* - Business logic over service-binding RPC only (no public routes in production). May own Drizzle schema under src/db/ and that database’s binding (exclusive owner).
queue-* - Queue-only consumers (queue() handler). Messages can be produced by worker-api, worker-*, or webhook-*. Use dual-handler layout when a local HTTP debug path is useful.
webhook-* - Public HTTP ingress for external provider callbacks; forward work via RPC or queues.
mcp-* - Public HTTP MCP servers; thin tools that call worker-* over RPC.
Do not create shared packages/db-* schema packages. Put Drizzle schema under the owning app’s src/db/ and keep one DB binding owner. Other apps reach that data via service-binding RPC (or a queue) - do not attach the same DB binding to multiple apps.
Frontend Applications
front-app - React SPA (Vite 8, Tailwind v4, TanStack Router/Query) deployed on Cloudflare Workers. Talks to worker-api over HTTP only - never via service bindings.
Where to put things
Task Location
New API route apps/worker-api/src/routes/.ts → mount in src/index.ts
HTTP Zod schemas packages/dtos-common/src/api/.ts
RPC / queue / webhook schemas packages/dtos-common/src//.ts (layer: rpc, queue, or webhook)
Shared string value set packages/enums-common/src/
Worker-local value set apps//src/enums/
DB schema (one owner) apps//src/db/ - never packages/db-*
Frontend API client apps/front-app/src/services/worker-api/.ts
Frontend page + route apps/front-app/src/pages/ + src/routes/
Local Worker secrets apps//.dev.vars (from .dev.vars.example)
Frontend env apps/front-app/.env.local (from .env.example)
Getting Started
Prerequisites
Node.js 22+ (see root package.json engines); we recommend fnm for version management
pnpm via the root packageManager field (Corepack recommended)
Cloudflare account only if you need make login / deploy / remote Worker features
Install and prepare
make install make login # optional - Cloudflare auth for remote Wrangler features make prepare # Husky pre-commit hooks
Copy env templates before the first run:
Workers: apps//.dev.vars.example → .dev.vars
Frontend: apps/front-app/.env.example → .env.local
Notes:
Prefer make install over raw pnpm install so workspace links stay consistent.
This repo pins pnpm via packageManager in the root package.json.
First successful run (verify locally)
Start all dev servers from the repo root:
make dev
Verify the API: GET http://localhost:8700/api/v1/health
Open the frontend: http://localhost:5174
Focused work on one package: make dev SCOPE=worker-api (see Scoping).
Make Commands
Command Description
install Install and link workspace packages
install-frozen Install with frozen lockfile (CI)
login Login to Cloudflare (repo-pinned Wrangler)
update Update dependencies to latest (rewrites pnpm catalog)
check Lint + format check (no typecheck)
ci Lint + format + check-types (local PR gate)
deploy Deploy all apps/workers (via Turborepo)
build Build all packages and apps (via Turborepo)
format Auto-fix formatting with oxfmt
lint Auto-fix lint issues with oxlint
dev Start all dev servers (via Turborepo)
preview Preview production builds locally (via Turborepo)
types Generate worker-configuration.d.ts in apps
check-types TypeScript across all workers and packages
prepare Install or reinstall Husky git hooks
husky-status Show Husky hooks status
skills-update Refresh locked agent skills (see make/README.md)
Scoping (pnpm / Turborepo)
Optional variables on any turbo-backed root target (dev, build, ci, …):
Variable Effect Example
SCOPE --filter= make dev SCOPE=worker-api
FILTER Raw turbo filter expression make build FILTER=...front-app...
AFFECTED Only changed packages vs base make ci AFFECTED=1
Development ports
Mnemonic: 87xx = Workers (gateway → business → queue → webhook → MCP → reserve). Frontends use Vite’s 51xx / 41xx.
Role Prefix Local HTTP ports
HTTP gateway worker-api 8700–8709
Business worker (RPC) worker-* 8710–8739
Queue-only consumer queue-* 8740–8759
Webhook ingress webhook-* 8760–8779
MCP server mcp-* 8780–8789
Growth reserve - 8790–8799
Frontend (Vite) front-* 5170–5199 (dev), 4170–4199 (preview)
Assigned registry
Service Path Dev Preview
worker-api apps/worker-api/wrangler.jsonc 8700 -
front-app apps/front-app/vite.config.ts 5174 4174
Notes:
Workers: set dev.port in wrangler.jsonc and monorepo.devPort in package.json. Use inspector_port: 0.
Frontends: set Vite server.port / preview.port with strictPort: true.
Assign the next free port in the role’s range. RPC and queue-only apps still get a local port for standalone wrangler dev, but have no public URL in production.
Prefer multi-config local runs when testing bindings (first -c is HTTP-primary).
- Create a New Cloudflare Worker
App Naming Nomenclature
Purpose Prefix Example
HTTP gateway worker-api (sticky) worker-api
Business logic (RPC) worker- worker-account
Queue-only consumer queue- queue-email
Webhook ingress webhook- webhook-example
MCP server mcp- mcp-tools
Frontend application front- front-app
Key Distinctions
Gateway (worker-api): Public HTTP only; validates requests and calls worker-* over RPC.
Business Workers (worker-*): RPC-only in production (WorkerEntrypoint); may own Drizzle schema under src/db/ and that database’s binding (exclusive). If they also consume queues, keep this prefix and use the dual-handler layout.
Queue-only (queue-*): queue() consumers with no public HTTP in production; may own schema when they are the sole writer for that data.
Webhook Workers (webhook-*): Public HTTP for external callbacks; forward via RPC or queues.
MCP Servers (mcp-*): Public HTTP MCP transport; thin tools that call worker-* over RPC - never rotate long-lived credentials on this surface.
Frontends (front-*): React + Vite; HTTP to the gateway only - never service bindings.
Do not create shared packages/db-* schema packages.
Scaffold checklist (copy from an existing app)
There is no generator CLI. Copy the closest sibling under apps/ and wire it into the monorepo:
Copy apps/worker-api (or another closest match) to apps/ (e.g. apps/worker-account).
Rename package.json name, wrangler.jsonc name, and any display strings.
Assign ports from the Development ports registry - set dev.port / inspector_port: 0 in wrangler.jsonc and monorepo.devPort in package.json.
Keep the 4-line Makefile that includes make/app.mk (see make/README.md).
Extend @repo/typescript-config/workers.json (or the matching preset); add .dev.vars.example.
Install and typegen:
make install make types
Copy wrangler patterns (compatibility_flags, observability, env.staging / env.production) from the existing app - see .cursor/rules/backend/workers-config.mdc.
- Develop a Specific Worker
Prefer scoped make from the repo root:
make dev SCOPE=worker-name
Or use the worker's Makefile:
cd apps/worker-name make dev
This runs the dev script defined in apps/worker-name/package.json
Open the port shown in your terminal (for example, http://localhost:8721)
Each worker Makefile exposes make dev, make format, make lint, make types, make check-types, make deploy
Testing Service Bindings Between Workers
Prefer a single multi-config wrangler dev (first -c is HTTP-primary):
wrangler dev -c apps/worker-api/wrangler.jsonc -c apps/worker-account/wrangler.jsonc
Or run each Worker in its own terminal (cd apps/worker-account && make dev, then cd apps/worker-api && make dev) and confirm service bindings show as connected in the wrangler output.
Dual-Handler Pattern
Use this layout for queue-* apps and for worker-* apps that also consume queues:
src/ ├── handlers/ │ ├── request.ts # Optional HTTP (local debug only) │ └── message.ts # Queue message consumption ├── services/ # Shared business logic └── index.ts # Minimal delegation entry point
queue-*: queue-only in production (no public HTTP).
worker-* with queues: keep the business prefix; expose RPC and optionally dual-handler HTTP for local testing.
- Environment Configuration
Each worker uses environment-specific configuration. Frontends use Vite env files (see apps/front-app/README.md).
Development Environment
Workers: .dev.vars (from .dev.vars.example) for local secrets and overrides
Frontend: .env.local (from .env.example) - only VITE_* keys reach the browser
Staging/Production Environments
Configuration: env.staging and env.production blocks in wrangler.jsonc
Deploy: wrangler deploy --env staging or --env production
Service Bindings: Connected to d
[truncated for AI cost control]