How freeCodeCamp Structures Its Monorepo with pnpm Workspaces and Turbo
The freeCodeCamp repository uses pnpm workspaces to manage multiple packages (client, API, curriculum, tools, and shared utilities) from a single root, while TurboRepo orchestrates build, test, and development pipelines with intelligent caching and parallel execution.
The freeCodeCamp open-source codebase is organized as a monorepo that consolidates its React frontend, Express backend, curriculum content, and internal tooling into a unified repository. This structure relies on pnpm workspaces to handle cross-package dependencies and TurboRepo to optimize task execution across the entire project.
pnpm Workspace Configuration
The foundation of the monorepo is defined in pnpm-workspace.yaml at the repository root. This file declares the glob patterns that pnpm uses to identify workspace packages:
client– The React-based frontend applicationapi– The Node.js/Express API servercurriculum– Markdown-based challenge content and parserstools/*– CLI utilities, challenge parsers, and seed scriptsshared– Reusable utilities consumed by other workspaces
Each directory contains its own package.json that defines the package's dependencies. Internal dependencies reference other workspaces using the workspace:* protocol, ensuring pnpm links them locally rather than fetching from the npm registry. This allows the api workspace to import shared utilities via import { util } from '@freecodecamp/shared', with pnpm resolving the symlink to the local shared directory.
TurboRepo Pipeline Orchestration
While pnpm manages package installation and linking, TurboRepo (configured in turbo.json) orchestrates task execution across workspaces. The configuration defines several pipelines that map to npm scripts in individual workspaces:
Core Pipelines
dev– Starts development servers for the client and API concurrently. Turbo runs these in parallel and watches for file changes, triggering hot reloads.build– Produces production bundles for the client and API. This pipeline depends ontypecheckandlintto ensure code quality before bundling.test– Executes unit and end-to-end tests across all workspaces, with Turbo caching test results to skip unchanged packages.lint– Runs ESLint and Stylelint across the monorepo.typecheck– Performs TypeScript compilation checks without emitting files.
Caching and Dependencies
Turbo uses the .turbo directory to cache task outputs, ensuring that subsequent runs of build or test complete instantly if inputs haven't changed. The dependsOn field in turbo.json establishes execution order—for example, the build pipeline waits for typecheck and lint to complete successfully before compiling assets.
Development Workflow and Cross-Workspace Commands
Developers interact with the monorepo through pnpm filters and Turbo pipelines. Common workflows include:
Installing Dependencies
Running pnpm install at the repository root reads pnpm-workspace.yaml, installs all external packages once, and creates symlinks for internal workspace dependencies.
pnpm install
Running Development Servers
The pnpm dev command invokes Turbo's dev pipeline, starting both the React client and Express API simultaneously:
pnpm dev
Targeting Specific Workspaces
Use the --filter flag to run commands in individual workspaces without affecting others:
# Run tests only for the API workspace
pnpm --filter api test
# Add a dependency to the client workspace
pnpm --filter client add lodash
The --filter flag respects the workspace graph, ensuring that if api depends on shared, changes to shared trigger appropriate updates in api.
Key Configuration Files
The monorepo structure is defined by several critical configuration files:
| File | Purpose | Location |
|---|---|---|
pnpm-workspace.yaml |
Declares workspace package globs | Repository root |
turbo.json |
Defines Turbo pipelines, caching, and task dependencies | Repository root |
package.json (root) |
Shared scripts (dev, build, test) that delegate to Turbo |
Repository root |
client/package.json |
React frontend dependencies and scripts | client/ |
api/package.json |
Express server dependencies and scripts | api/ |
curriculum/package.json |
Curriculum parser and content management | curriculum/ |
shared/package.json |
Shared utilities consumed by other workspaces | shared/ |
tools/*/package.json |
Individual tooling packages | tools/*/ |
These files work together to ensure that pnpm install sets up the correct dependency graph, while turbo run executes tasks efficiently across the defined workspaces.
Summary
- pnpm workspaces manage the monorepo's package graph via
pnpm-workspace.yaml, linking internal packages with theworkspace:*protocol and deduplicating external dependencies. - TurboRepo orchestrates cross-package scripts through
turbo.json, providing parallel execution, intelligent caching via the.turbodirectory, and dependency ordering viadependsOn. - The repository organizes code into logical workspaces:
client(React),api(Express),curriculum(content),shared(utilities), andtools/*(scripts). - Developers use
pnpm installfor setup,pnpm devfor concurrent development, andpnpm --filter <workspace>for targeted operations.
Frequently Asked Questions
How does pnpm handle dependencies between workspaces?
pnpm uses the workspace:* protocol in package.json files to link internal packages. When one workspace depends on another (e.g., api importing from shared), pnpm creates a symlink in node_modules pointing to the local workspace directory rather than fetching from the npm registry. This ensures changes in shared are immediately reflected in dependent packages.
What is the purpose of the dependsOn field in turbo.json?
The dependsOn field defines task dependencies and execution order within Turbo pipelines. For example, if the build pipeline lists "dependsOn": ["typecheck", "lint"], Turbo will run type checking and linting first, and only proceed to build if those tasks succeed. This guarantees that code quality checks pass before expensive compilation steps run.
Can I run commands in only one workspace without affecting others?
Yes. Use the --filter flag with pnpm to target specific workspaces. For example, pnpm --filter api test runs the test script only in the api workspace, while pnpm --filter client add react installs a dependency exclusively for the client package. This filtering respects the workspace dependency graph, so if the filtered package depends on other workspaces, pnpm ensures those dependencies are available.
How does TurboRepo improve CI/CD performance for the monorepo?
TurboRepo caches task outputs in the .turbo directory and can share that cache across CI runs via remote caching. When a workspace's source files and dependencies haven't changed, Turbo skips re-running tests or builds and restores the cached results instantly. This dramatically reduces CI times for large monorepos like freeCodeCamp, where only modified packages need re-testing rather than the entire codebase.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →