This project is a full-stack TypeScript monorepo.
- Frontend: React 19, Vite, Zustand (State), TanStack Router (Routing), tRPC (API Client).
- Backend: Node.js, Express, tRPC (API Server), Drizzle ORM (Postgres).
- Infrastructure: Docker Compose managing Node containers, PostgreSQL 18.
- Tooling: pnpm (Workspaces), ESLint (Strict Linting), Prettier.
/ ├── apps/ │ ├── client/ # Vite + React Frontend │ │ ├── src/ │ │ │ ├── components # UI Components │ │ │ ├── routes/ # File-Based Routes │ │ │ ├── store/ # Zustand Stores │ │ │ ├── main.tsx # Entry & Providers │ │ │ └── trpc.ts # tRPC Client Setup │ └── server/ # Express + tRPC Backend │ ├── src/ │ │ ├── db/ # Drizzle Schema & Config │ │ ├── lib/ # Shared business logic | | ├── controller/ # Functions that are assigned to routes in router.ts │ │ └── router.ts # tRPC API Routers ├── packages/ │ └── shared/ # Shared Zod Schemas & Types ├── docker/ # Dockerfiles for apps └── docker-compose.yml # Local development orchestration - Docker & Docker Compose
- Node.js (Optional, for local install)
- pnpm (Optional)
- Environment: Managed via
.envfile (copied from.env.example). - Linting: Run
./dev.sh lintto check code quality. Configuration ineslint.config.js.
We provide a helper script to manage the application without needing local Node tools.
./dev.sh helpCommon commands:
./dev.sh build: Rebuild Docker images./dev.sh start: Start the stack./dev.sh stop: Stop the stack./dev.sh lint: Run linter./dev.sh db:generate: Generate migrations./dev.sh db:migrate: Run migrations
The entire stack runs in Docker with hot-reloading enabled.
./dev.sh startUse ./dev.sh stop to stop the stack and clean up.
- Frontend: http://localhost:5173 (Vite Dev Server)
- Backend: http://localhost:4000 (Express Server)
- Hot Reload: Changes to
apps/clientorapps/serversource files will automatically trigger updates in the running containers. - Logs: View logs in the terminal running Docker Compose.
Modify the schema in apps/server/src/db/schema.ts. Run migrations (from the host or inside container):
./dev.sh db:generate ./dev.sh db:migrateDefine input validation schemas in packages/shared/src/index.ts.
import { z } from 'zod'; export const newUserSchema = z.object({ ... });Add a new procedure in apps/server/src/router.ts.
export const appRouter = router({ newUser: publicProcedure .input(newUserSchema) // Type-safe input from shared package .mutation(async ({ input }) => { // Database logic here }), });- Add Route: Create a new file in
apps/client/src/routes/.about.tsx->/aboutposts/$postId.tsx->/posts/123
- Fetch Data: Use the tRPC hook inside the component.
import { createFileRoute } from "@tanstack/react-router"; export const Route = createFileRoute("/about")({ component: About, }); function About() { return <div>About Page</div>; }Define new stores in apps/client/src/store/.
export const useMyStore = create((set) => ({ count: 0 }));Use in components:
const count = useMyStore((state) => state.count);