TypeScript Monorepo Magic: Organize, Build, and Ship…
    Neura MarketNeura Market/Midjourney
    ChatGPTChatGPTClaudeClaudeGeminiGeminiCursorCursorGrokGrokPerplexityPerplexityMidjourneyMidjourney
    DeepSeekDeepSeekCoPilotCoPilotStable DiffusionStable Diffusion
    View All Directories
    OverviewPromptsBlogVideosGuidesCoursesCommunityStylesTrending
    MidjourneyBlogTypeScript Monorepo Magic: Organize, Build, and Ship Multi-Package Apps
    Back to Blog
    TypeScript Monorepo Magic: Organize, Build, and Ship Multi-Package Apps
    programming

    TypeScript Monorepo Magic: Organize, Build, and Ship Multi-Package Apps

    M Hossein June 2, 2026
    0 views

    What is a Monorepo? A monorepo is a single Git repository that contains multiple distinct...


    How Monorepos Work — Using shrimp-monorepo as the Example


    What is a Monorepo?

    A monorepo is a single Git repository that contains multiple distinct packages or applications. The alternative is a polyrepo: one repo per app or package.

    This repo is shrimp-monorepo. It contains:

    shrimp-monorepo/
    ├── apps/
    │   ├── api/            ← Node.js backend (Elysia + gRPC)
    │   ├── client-user/    ← React frontend for end users
    │   └── client-admin/   ← React frontend for admins
    ├── packages/
    │   ├── shared-types/   ← TypeScript types used everywhere
    │   ├── ui/             ← Shared React components
    │   ├── proto/          ← Protobuf definitions + generated code
    │   ├── grpc-client/    ← gRPC client wrapper
    │   └── db-schema/      ← Drizzle ORM schema
    └── tooling/
        └── typescript/     ← Shared tsconfig files
    

    The core idea: code that is needed by multiple apps lives in packages/, and all apps can import it directly — no npm publishing required.


    Tool 1: pnpm Workspaces — The Foundation

    pnpm is the package manager. Workspaces are its monorepo feature.

    How it's declared

    pnpm-workspace.yaml:

    packages:
      - 'apps/*'
      - 'packages/*'
      - 'tooling/*'
    

    This tells pnpm: treat every directory in these globs as a package. That's it — three lines define the entire workspace.

    What this enables

    When you run pnpm install at the repo root, pnpm:

    1. Reads every package.json in every workspace directory
    2. Hoists shared external dependencies into a single node_modules at the root
    3. Creates symlinks for internal packages so they can import each other

    The workspace:* protocol

    Look at apps/api/package.json:

    "dependencies": {
      "@shrimp/db-schema": "workspace:*",
      "@shrimp/proto": "workspace:*",
      "@shrimp/shared-types": "workspace:*"
    }
    

    workspace:* means: don't fetch this from npm — link it from this workspace instead. When api imports @shrimp/db-schema, Node resolves it to packages/db-schema/src/index.ts on disk via a symlink in node_modules/.pnpm.

    This is what makes internal sharing work without publishing packages.

    Why pnpm over npm/yarn?

    pnpm uses a content-addressable store (the .pnpm-store/ directory in this repo). Every version of every package is stored once globally. Workspaces get hard links to the store, not copies. This means:

    • Faster installs
    • Significantly less disk space
    • Strict by default — packages can't accidentally import things they didn't declare

    Tool 2: Turborepo — Task Orchestration and Caching

    pnpm workspaces handle dependencies. Turbo handles tasks (build, test, lint, etc.).

    The problem Turbo solves

    If you run pnpm run build in a repo with 8 packages, you have to figure out the order yourself. proto must build before grpc-client, which must build before api. Do it wrong and you get stale or missing types.

    Also, if nothing in packages/ui changed, you shouldn't need to rebuild it.

    Turbo solves both: dependency-aware task ordering + task result caching.

    turbo.json — the pipeline

    {
      "tasks": {
        "build": {
          "dependsOn": ["^build"],
          "outputs": ["dist/**", ".output/**", "generated/**"]
        },
        "dev": {
          "cache": false,
          "persistent": true
        },
        "lint": {
          "dependsOn": ["^build"]
        },
        "typecheck": {
          "dependsOn": ["^build"]
        },
        "test:unit": {
          "dependsOn": ["^build"],
          "outputs": ["coverage/**"]
        }
      }
    }
    

    Key concepts:

    • "dependsOn": ["^build"] — the ^ prefix means build all packages that this package depends on first. So when building @shrimp/api, Turbo automatically builds @shrimp/proto, @shrimp/db-schema, and @shrimp/shared-types beforehand, in the right order.

    • "dependsOn": ["build"] (no ^) — run the same package's build task first. Used by test:e2e: build the app before running E2E tests.

    • "cache": false — never cache this task. dev is persistent/interactive, so caching makes no sense.

    • "persistent": true — this task runs forever (a dev server). Turbo knows not to treat it as something that finishes.

    • "outputs" — Turbo hashes these paths to know what "done" looks like. If inputs haven't changed and outputs still exist, Turbo skips the task entirely (cache hit).

    How the build graph flows

    proto:generate
        ↓
    @shrimp/proto (build)
        ↓
    @shrimp/grpc-client (build)
        ↓
    apps/api (build)
    apps/client-user (build)
    apps/client-admin (build)
    

    @shrimp/shared-types, @shrimp/db-schema, and @shrimp/ui also build in parallel before their consumers, since they have no inter-dependencies.

    Filtering — run tasks for specific packages

    The root package.json uses --filter for targeted dev:

    "dev:user":  "turbo run dev --filter=@shrimp/client-user",
    "dev:admin": "turbo run dev --filter=@shrimp/client-admin",
    "dev:api":   "turbo run dev --filter=@shrimp/api"
    

    --filter accepts package names, directory globs, or git-based expressions.

    Affected-only in CI

    The CI workflow uses the most powerful filter:

    run: pnpm turbo run typecheck lint test:unit build --affected
    

    --affected uses the git diff against the base branch to determine which packages changed, then runs tasks only on those packages (and their dependents). A PR that only touches packages/ui won't re-run api tests.


    Tool 3: Shared Packages — How Internal Code is Shared

    Source-level packages (no compilation step)

    Most packages in this repo point directly at TypeScript source:

    packages/shared-types/package.json:

    {
      "name": "@shrimp/shared-types",
      "main": "./src/index.ts",
      "types": "./src/index.ts",
      "exports": {
        ".": "./src/index.ts"
      }
    }
    

    There is no build script. When @shrimp/api imports @shrimp/shared-types, it imports .ts files directly. The consuming app's bundler or TypeScript compiler handles the compilation.

    This is sometimes called the "internal packages" pattern — no dist/ folder, no compile step, just source. It works because all consumers in the monorepo are TypeScript themselves.

    Packages that do need a build step

    @shrimp/proto generates TypeScript from .proto files:

    "scripts": {
      "proto:generate": "pnpm exec protoc --ts_out ./generated ...",
      "build": "pnpm run proto:generate"
    }
    

    Turbo's "dependsOn": ["^build"] ensures proto:generate runs before anything that consumes @shrimp/proto.


    Tool 4: Shared TypeScript Config — tooling/typescript

    Rather than duplicating 25 lines of compilerOptions across 8 packages, this repo centralizes TypeScript config in tooling/typescript/.

    tooling/typescript/tsconfig.base.json — strict settings shared by all packages:

    {
      "compilerOptions": {
        "strict": true,
        "noUnusedLocals": true,
        "noUncheckedIndexedAccess": true,
        "moduleResolution": "bundler",
        ...
      }
    }
    

    tooling/typescript/tsconfig.react.json — extends base, adds JSX:

    {
      "extends": "./tsconfig.base.json",
      "compilerOptions": { "jsx": "react-jsx" }
    }
    

    Each package inherits via extends:

    apps/api/tsconfig.json:

    {
      "extends": "../../tooling/typescript/tsconfig.base.json",
      "compilerOptions": {
        "outDir": "./dist",
        "rootDir": "./src"
      }
    }
    

    packages/ui/tsconfig.json:

    {
      "extends": "../../tooling/typescript/tsconfig.react.json",
      "include": ["src/**/*.ts", "src/**/*.tsx", "tests/**/*.ts"]
    }
    

    Why this matters: change one setting in tsconfig.base.json and it propagates to every package in the repo. No drift, no "why is strict mode off in this one package" surprises.


    Tool 5: Biome — Unified Linting and Formatting

    Biome replaces ESLint + Prettier with a single fast tool. A single biome.json at the root applies to the entire repo.

    biome.json key config:

    {
      "linter": {
        "rules": {
          "correctness": {
            "noUnusedVariables": "error",
            "noUnusedImports": "error"
          },
          "style": { "useConst": "error" }
        }
      },
      "formatter": {
        "indentStyle": "tab",
        "indentWidth": 2,
        "lineWidth": 100
      }
    }
    

    Each package runs biome check . as its lint script, but the rules are defined once. This is the same pattern as TypeScript config inheritance: one source of truth, many consumers.


    Tool 6: Git Hooks — Enforcing Quality at Commit Time

    The repo uses a custom git hooks directory instead of the default .git/hooks. This means hooks can be committed and versioned.

    Setup (runs on pnpm install via the prepare lifecycle):

    "prepare": "git config core.hooksPath .githooks"
    

    .githooks/pre-commit:

    #!/usr/bin/env sh
    pnpm precommit:staged
    

    scripts/biome-staged.mjs — runs Biome only on staged files:

    const staged = spawnSync("git", ["diff", "--cached", "--name-only", "-z", ...]);
    // ... filters to .ts/.tsx/.js/.json files
    spawnSync("pnpm", ["exec", "biome", "check", "--no-errors-on-unmatched", ...files]);
    

    The key insight: it doesn't lint the whole repo on every commit — only the files currently staged. This keeps commits fast while still enforcing quality.


    The Full Picture: How These Tools Interact

    Developer commits
        ↓
    git pre-commit hook (.githooks/pre-commit)
        ↓ runs biome-staged.mjs
        ↓ Biome checks only staged files
        ↓ (fails → abort commit; passes → continue)
        ↓
    pnpm workspace resolves internal deps via workspace:* symlinks
        ↓
    turbo run build
        ↓ reads turbo.json pipeline
        ↓ resolves dependency graph from package.json deps
        ↓ builds packages in order: proto → grpc-client → api/clients
        ↓ caches outputs in .turbo/
        ↓
    CI: turbo run ... --affected
        ↓ diffs against main
        ↓ runs only impacted packages
        ↓ TypeScript config inherited from tooling/typescript/
        ↓ Biome config inherited from root biome.json
    

    Summary: Why This Stack

    ProblemToolMechanism
    Share code without publishing to npmpnpm workspacesworkspace:* + symlinks
    Run tasks in dependency orderTurbo"dependsOn": ["^build"]
    Don't rebuild unchanged packagesTurbo cacheoutputs hashing
    Run tasks on only changed code in CITurbo --affectedgit diff
    Consistent TypeScript configtooling/typescriptextends inheritance
    One linter/formatter configBiome root biome.jsonsingle config, all packages
    Enforce quality before commitsGit hooks (.githooks/)staged-file Biome check

    The monorepo isn't one tool — it's these tools composing together. pnpm handles what exists, Turbo handles when things run, and the tooling layer handles how they're configured.

    Tags

    programmingtutorialarchitecturetypescript

    Comments

    More Blog

    View all
    Five Gemma-4 models, one accelerator: what porting E2B 31B to AWS Inferentia2 taught megemma

    Five Gemma-4 models, one accelerator: what porting E2B 31B to AWS Inferentia2 taught me

    I ported the whole Gemma-4 family — E2B, E4B, 12B, 31B, and the 26B-A4B MoE — to run on...

    X
    xbill
    Hey DEV, I'm Tobore. Let's actually connect.community

    Hey DEV, I'm Tobore. Let's actually connect.

    Hey DEV, I'm Tobore. Let's actually connect. I've been on here for a while now, mostly writing and...

    L
    Laurina Ayarah
    I burned through thousands of AI tokens. Then a friend did it for freeai

    I burned through thousands of AI tokens. Then a friend did it for free

    (yep, kinda clickbait, just for the funsies 😊) At the beginning of the year, I relaunched my...

    P
    Paulo Henrique
    Claude might be saturating your machineai

    Claude might be saturating your machine

    My laptop was sitting idle with the fan at full tilt. Nothing was running that I knew of. The culprit...

    S
    Sidhant Panda
    Automated GitHub Code Reviews Using Google Geminigithubactions

    Automated GitHub Code Reviews Using Google Gemini

    I Built a Thing! TL;DR — Google Gemini-based Pull Request reviews and Issue Triaging for...

    D
    Darren "Dazbo" Lester
    What is an "agentic harness," actually?ai

    What is an "agentic harness," actually?

    I've been hearing the word "harness" thrown around a lot lately. I assumed it just meant "the IDE" or...

    T
    Tilde A. Thurium

    Stay up to date

    Get the latest Midjourney prompts, rules, and resources delivered to your inbox weekly.

    Neura Market LogoNeura Market

    Discover the best AI prompts, plugins, and resources for Midjourney and more.

    Content Types

    • Rules
    • Prompts
    • MCPs
    • Agents
    • Guides

    Platforms

    • ChatGPT Directory
    • Claude Directory
    • Gemini Directory
    • Cursor Directory
    • Grok Directory
    • Perplexity Directory
    • DeepSeek Directory
    • CoPilot Directory
    • Stable Diffusion Directory
    • Midjourney Directory
    • All Directories

    Resources

    • Blog
    • Documentation
    • Help Center
    • Marketplace

    Legal

    • Privacy Policy
    • Terms of Service

    © 2026 Neura Market. All rights reserved.

    |

    Not affiliated with any AI platform vendors.

    Neura Market

    Custom AI Systems & Services

    Our team of experienced AI builders will help build custom AI systems, workflows, and solutions for your business.

    Request custom work

    Ready-made automations for this

    Workflows from the Neura Market marketplace related to this Midjourney resource

    • Create Animated Stories Using GP-4.0-mini, Midjourney, Kling, and Creatomate APIn8n · $24.99 · Related topic
    • Backup Workflows to Git Repository on Gitean8n · $14.99 · Related topic
    • Backup Workflows to Git Repository on GitHubn8n · $14.99 · Related topic
    • Automate GitHub File Updates and Local Repository Syncn8n · $9.99 · Related topic
    Browse all workflows