AI News HubLIVE
In-site rewrite5 min read

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.

SourceHacker News AIAuthor: brulenaudet

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).

  1. 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.

  1. 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.

  1. 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]