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 check
Husky + lint-staged automatically run formatting on commits.
- Make sure
CORS_ORIGIN
matches the web app URL in development (defaulthttp://localhost:3001
). - The web app reads
NEXT_PUBLIC_SERVER_URL
to reach the server (defaulthttp://localhost:3000
).
This project was initially bootstrapped with Better-T-Stack.
First, install the dependencies:
pnpm install
This project uses PostgreSQL with Drizzle ORM.
-
Make sure you have a PostgreSQL database set up.
-
Update your
apps/server/.env
file with your PostgreSQL connection details. -
Apply the schema to your database:
pnpm db:push
Then, run the development server:
pnpm dev
Open 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