# Setup : Monorepo-turborepo-tRPC

## Multi-repo VS Monorepo

*   Multirepo - react-frontend, express backend, postgress-db, redis server
    
*   Monorepo - sigle repo > one repo can contain frontent, backend, shared utils and types etc example project
    
*   apps -- web (fully deployed app) -- api // fully deployed backend
    
*   packages -- shared // common utils, db connection, schema
    

**Benefits => code sharing, atomic commits, better team collab, unified cicd & tooling**

**PNPM => Performant NPM ,like npm,yarn or bun** // popular in monorepo

*   Fast
    
*   Disk space efficient
    
*   insted of duplicate is store in central place and each time take refrence symlinks
    

PNPM vs NPM

pnpm-workspaces > depened on each other, import each other, share efficiently, install and run packages together

## Project setup

`npm i pnpm`  
`mkdir monorepo && cd monorepo`  
`pnpm init` // update with required things

> package.json

```json
{
  "name": "monorepo",
  "private": true,
  "types": "module",
  "packageManager": "pnpm@10.33.4"
}
```

### Folder setup

`mkdir apps packages apps/api apps/web packages/utils`

> pnpm-workspace.yaml

the line that makes it a workspace. Any folder under apps/ or packages/ is now a linkable package:

```yml
packages:
  - "apps/*"
  - "packages/*"
allowBuilds:
  esbuild: true
```

### Shared package setup

> packages/utils

`pnpm init`  
`pnpm add -D typescript`  
`pnpm add zod`  
`tsc --init`

> packages/utils/package.json

```json
{
  "name": "@monorepo/utils",
  "main": "index.js",
  "type": "module",
  "scripts": {
    "dev": "tsc --watch",
    "build": "tsc"
  },
  "exports": {
    ".": "./src/index.ts"
  },
  "packageManager": "pnpm@10.33.4",
  "devDependencies": {
    "typescript": "^6.0.3"
  },
  "dependencies": {
    "zod": "^4.4.3"
  }
}
```

> packages/utils/tsconfig.json

```json
{
    // Visit https://aka.ms/tsconfig to read more about this file
    "compilerOptions": {
        // File Layout
        "rootDir": "./src", // ts
        "outDir": "./dist", // js

        // Environment Settings
        // See also https://aka.ms/tsconfig/module
        "module": "nodenext",
        "target": "esnext",
        "types": [],
        // For nodejs:
        // "lib": ["esnext"],
        // "types": ["node"],
        // and npm install -D @types/node

        // Other Outputs
        "sourceMap": true,
        "declaration": true,
        "declarationMap": true,

        // Stricter Typechecking Options
        "noUncheckedIndexedAccess": true,
        "exactOptionalPropertyTypes": true,

        // Style Options
        // "noImplicitReturns": true,
        // "noImplicitOverride": true,
        // "noUnusedLocals": true,
        // "noUnusedParameters": true,
        // "noFallthroughCasesInSwitch": true,
        // "noPropertyAccessFromIndexSignature": true,

        // Recommended Options
        "strict": true,
        "jsx": "react-jsx",
        "verbatimModuleSyntax": true,
        "isolatedModules": true,
        "noUncheckedSideEffectImports": true,
        "moduleDetection": "force",
        "skipLibCheck": true
    }
}


```

> packages/utils/src/index.ts

Create Zod schema // for frontend and backend validation

```ts
import { z } from 'zod';
export const createUserSchema = z.object({
    name: z.string().min(3, "Name must be at least 3 characters long"),
    email: z.email("Invalid email address"),
    password: z.string().min(6, "Password must be at least 6 characters long")
})

export type CreateUserSchema = z.infer<typeof createUserSchema>; // zod will create schema like this
```

> packages/utils/package.json export the index file

```json
"exports":{
    ".":"./src/index.ts"
}
```

> packages/utils `pnpm build`

### Backend setup

> apps/api

Backend setup for API

`pnpm init`  
`pnpm add express @types/express -D typescript tsx @types/node`  
`tsc --init`

**update .tsconfig.json**

*   rootDir
    
*   outDir
    
*   lib
    
*   types
    

> apps/api/package.json update with

```json
{
  "name": "@monorepo/api",
  "main": "index.js",
  "type": "module",
  "scripts": {
    "dev": "tsx watch src/index.ts",
    "build": "tsc"
  },
  "packageManager": "pnpm@10.33.4",
  "devDependencies": {
    "@types/express": "^5.0.6",
    "@types/node": "^26.0.0",
    "express": "^5.2.1",
    "tsx": "^4.22.4",
    "typescript": "^6.0.3"
  }
}

```

> apps/api/src/index.ts

Basic get request

```ts
import express  from "express";
const app = express();
const PORT = 5000;
app.use(express.json());

app.get("/", (req, res) => {
    return res.json({ message: "Hello from API" });
})

app.listen(PORT, () => {
    console.log(`Server is running on port ${PORT}`);
});
```

#### Now import ZOD schema to the backend api

> apps/api/package.json name:"@projectName/api" // "@repo/api" dependcies:{ "@projectName/utils": "workspace:\*" // indicate get from local not version } apps/api `pnpm i` // insalled and symlinks created

> apps/api/src/index.ts Import Zod from symlinks or shared folder

```ts
import { createUserSchema } from "@monorepo/utils";
app.post("/users", (req, res) => {
    const result = createUserSchema.safeParse(req.body);
    if(!result.success) {
        const message = result.error.issues.map((issue) => issue.message).join(", ")
        return res.status(400).json({success:false, message:message });
    }
    console.log(result.data);
    return res.json({success:true, message: "User created" });
});
```

### Frontend setup

> apps/web

`pnpm create next-app .`  
remove **pnpm-workspace.yaml** its confilict remove **public** folder

> package/web/package.json update the dependencies "name": "@monorepo/web",// "@repo/utils" `pnpm i`

> package/web/app/page.tsx

```tsx
"use client";
import { createUserSchema } from "@monorepo/utils";
import axios from "axios";
import { useState } from "react";
import type { SubmitEvent } from "react";

export default function Home() {
  const [formData, setFormData] = useState({
    name: "",
    email: "",
    password: "",
  });
  const [errors, setErrors] = useState("");
  const [success, setSuccess] = useState(false);
  const handleSubmt = async (e: SubmitEvent<HTMLFormElement>) => {
    setSuccess(false);
    setErrors("");
    e.preventDefault();
    const result = createUserSchema.safeParse(formData);
    if (!result.success) {
      const message = result.error.issues
        .map((issue) => issue.message)
        .join(", ");
      setErrors(message);
      return alert(message);
    }
    console.log(result.data);
    try {
      const res = await axios.post("http://localhost:5000/users", result.data);
      console.log(res);
      setSuccess(true);
    } catch (error) {
      console.log(error);
    }
  };
  return (
    <form onSubmit={handleSubmt} noValidate>
      {errors && errors.split(", ").map((error, i) => <p key={i}>{error}</p>)}
      {success && <p>Success</p>}
      <input
        type="text"
        onChange={(e) => setFormData({ ...formData, name: e.target.value })}
      />
      <input
        type="email"
        onChange={(e) => setFormData({ ...formData, email: e.target.value })}
      />
      <input
        type="password"
        onChange={(e) => setFormData({ ...formData, password: e.target.value })}
      />
      <button type="submit">Submit</button>
    </form>
  );
}


```

> package/web `pnpm i axios` and update package/web/app/page.tsx post requiest on submit ""

```ts
const res = await axios.post("http://localhost:5000/users", result.data)
```

**check for cors error and make form submit**

### all in one setup for all

for all run and build from one place

> package.json

```json
"scripts": {
    "dev": "pnpm -r --parallel dev",
    "build": "pnpm -r build"
  }
```

## TurboRepo setup

package is depandend on each other (dependcy graph). Build in Right order

### Turbo repo // turborepo.dev

*   Build order manage
    
*   caching
    
*   task orchestration
    
*   parrlel excuation
    

### Add turbo in project

`pnpm add -D turbo -w` // -w is for do it now

> turbo.json

```json
{
    "$schema":"https://turbo.build/schema.json",
    "ui":"tui", // terminal ui to looks good the console the logs for each server
    "tasks":{
        "build": {
            "dependsOn": ["^build"], // check for dependent other build (resolve dependecy graph)
            "outputs": ["dist/**", "next/**", "!dist/**/node_modules/**", "!next/**/node_modules/**", "!.next/cache/**"] //caching
        },
        "dev":{
            "cache": false, // no need to cache
            "persistent": true // long running
        },
        "prettier":{
            "cache": false,
            "persistent": true
        },
        "lint":{
            "cache": false,
            "persistent": true
        }
    }
}
```

> package.json

```json
  "scripts": {
    "dev": "turbo dev",
    "build": "turbo build"
  },
```

at Root folder `pnpm dev` `pnpm build`

## tRPC (Remote procedure calls)

clinet will call the remote server RPC concept call without "stings" call the direct methods impelentation (gRPC and tRPC)

### Packages or share utils

> packages/trpc `pnpm init` `pnpm add -D typescript` `pnpm add @trpc/server` `tsc --init` packages/trpc/package.json

```json
"name":"@monorepo/trpc"
private:true
export:trpc
dependies:{
    "@monorepo/utils": "workspace:*",
}
scripts:{
    "dev":"tsc --watch",
    "build":"tsc"
}

```

> packages/trpc/src/trpc.ts

```ts
import { initTRPC } from '@trpc/server';
const t = initTRPC.create();
export const router = t.router; // rotuer > function declare
export const procedure = t.procedure; // functions
```

> packages/trpc/src/router.ts

```ts
import { procedure, router } from "./trpc.js";
import { createUserSchema } from "@peer-class/utils";
export const appRouter = router({
    health: procedure.query(() => {
        return {
            message: "healthy"
        }
    }),
    register: procedure
        .input(createUserSchema)
        .mutation(({ input }) => {
            // TODO: persist user to DB
            console.log("register", input);
            return {
                message: "User Registered Successfully"
            }
        }),
});

export type AppRouter = typeof appRouter;
```

> packages/trpc/src/index.ts

```ts
export { appRouter } from './router.js';
export type { AppRouter } from './router.js';
```

`pnpm i`

### Backend

> apps/api/ `pnpm add @trpc/server`

add trpc package at package.json `pnpm i`

> apps/api/src/index.ts

```ts
import { createExpressMiddleware } from "@trpc/server/adapters/express";
import { appRouter } from "@peer-class/trpc";
// remove app.get (no need of rest api)
app.use(
    "/trpc",
    createExpressMiddleware({
        router: appRouter
    })
);

```

### Frontend

> apps/web `pnpm add @trpc/client @tanstack/react-query @trpc/react-query` app/web/trpc/trpc.ts

```ts
import { createTRPCReact } from "@trpc/react-query";
import type { AppRouter } from "@peer-class/trpc";
export const trpc = createTRPCReact<AppRouter>();
```

> app/web/trpc/Provider.tsx

```tsx
'use client';
import { useState } from "react";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { httpBatchLink } from "@trpc/client";
import { trpc } from "./trpc";

export function Provider({ children }: { children: React.ReactNode }) {
    // React Query client — manages server state caching, background refetching, etc.
    const [queryClient] = useState(() => new QueryClient());

    // tRPC client — batches multiple procedure calls into a single HTTP request to /trpc
    const [trpcClient] = useState(() =>
        trpc.createClient({
            links: [
                httpBatchLink({
                    url: "http://localhost:5000/trpc",
                }),
            ],
        })
    );

    // trpc.Provider bridges tRPC with React Query so tRPC hooks use the shared queryClient
    return (
        <trpc.Provider client={trpcClient} queryClient={queryClient}>
            <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
        </trpc.Provider>
    );
}

/*
 * What this file does:
 * ---------------------
 * Sets up the tRPC + React Query context for the entire Next.js app.
 *
 * - QueryClient      → React Query's cache store. Holds all server-state (queries/mutations).
 * - trpcClient       → HTTP client that sends batched requests to the Express backend at
 *                      http://localhost:5000/trpc using tRPC's httpBatchLink.
 * - trpc.Provider    → Makes tRPC hooks (trpc.*.useQuery / useMutation) available in the tree.
 * - QueryClientProvider → Makes React Query hooks (useQuery, useMutation) available in the tree.
 *
 * Both providers share the same queryClient so tRPC and React Query stay in sync.
 * Wrap the root layout (layout.tsx) with this Provider to enable tRPC across the whole app.
 */
```

> apps/web/app/layout.tsx

```tsx
import { Provider } from "../trpc/Provider";
<Provider>{children}</Provider> // get everywhere for query and mutations
```

> apps/web/app/health/page.tsx

```tsx
'use client';
import { trpc } from "@/trpc/trpc"
export default function Health(){
    const health = trpc.health.useQuery();
    console.log(health.data);
    return (
        <div>
            <h1>Healthy</h1>
        </div>
    )
}
```

Final commad at root to run all  
`pnpm dev`  
`pnpm build`
