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