Modern TypeScript monorepo with a Next.js web app, a Hono server using oRPC, shared contracts, Drizzle ORM, and PostgreSQL.
- TypeScript-first across apps and packages
- Next.js 15 (Turbopack) frontend
- Hono server with oRPC (RPC + OpenAPI) and Better-Auth
- Drizzle ORM with PostgreSQL and Docker Compose helpers
- TailwindCSS + shadcn/ui components
- Biome + Ultracite for formatting/linting and Husky + lint-staged
- Node.js and pnpm installed
- Docker (optional, recommended for local PostgreSQL)
- Install dependencies
pnpm install- Configure environment variables
Create apps/server/.env:
DATABASE_URL=postgresql://postgres:password@localhost:5432/nextjs-contract-based-monorepo
CORS_ORIGIN=http://localhost:3001
# Optional, required for /ai endpoint
OPENAI_API_KEY=your-openai-api-key
Create apps/web/.env.local:
NEXT_PUBLIC_SERVER_URL=http://localhost:3000
- Start PostgreSQL (via Docker Compose)
pnpm db:start- Apply the database schema
pnpm db:push- Run the apps in development
pnpm dev- Web: http://localhost:3001
- API: http://localhost:3000
GET /→ Health check: returns "OK"POST /ai→ AI text streaming (requiresOPENAI_API_KEY)POST/GET /api/auth/**→ Better-Auth routesPOST/GET /rpc/**→ oRPC endpointsPOST/GET /api/**→ OpenAPI HTTP endpoints generated from contract
nextjs-contract-based-monorepo/
├── apps/
│ ├── web/ # Next.js frontend
│ └── server/ # Hono + oRPC backend (Drizzle, Better-Auth)
└── packages/
└── contract/ # Shared oRPC contract and Zod schemas
Root-level scripts (run from repo root):
pnpm dev: Run all apps in dev modepnpm build: Build all appspnpm check-types: Check types across packagespnpm dev:web: Run just the web apppnpm dev:server: Run just the server
Database helpers (proxy to apps/server):
pnpm db:push: Push Drizzle schemapnpm db:studio: Open Drizzle Studiopnpm db:generate: Generate migrationspnpm db:migrate: Apply migrationspnpm db:start: Start Postgres via Docker Composepnpm db:watch: Attach to logs (foreground)pnpm db:stop: Stop containerspnpm db:down: Remove containers and volumes
- Format/lint with Biome (writes fixes):
pnpm check- Check with Ultracite (no write):
pnpm dlx ultracite checkHusky + lint-staged automatically run formatting on commits.
- Make sure
CORS_ORIGINmatches the web app URL in development (defaulthttp://localhost:3001). - The web app reads
NEXT_PUBLIC_SERVER_URLto reach the server (defaulthttp://localhost:3000).
This project was initially bootstrapped with Better-T-Stack.
First, install the dependencies:
pnpm installThis project uses PostgreSQL with Drizzle ORM.
-
Make sure you have a PostgreSQL database set up.
-
Update your
apps/server/.envfile with your PostgreSQL connection details. -
Apply the schema to your database:
pnpm db:pushThen, run the development server:
pnpm devOpen http://localhost:3001 in your browser to see the web application. The API is running at http://localhost:3000.
nextjs-contract-based-monorepo/
├── apps/
│ ├── web/ # Frontend application (Next.js)
│ └── server/ # Backend API (Hono, ORPC)
pnpm dev: Start all applications in development modepnpm build: Build all applicationspnpm dev:web: Start only the web applicationpnpm dev:server: Start only the serverpnpm check-types: Check TypeScript types across all appspnpm db:push: Push schema changes to databasepnpm db:studio: Open database studio UI