Managing Full Stack Monorepos with Turborepo and pnpm Workspaces

In modern full-stack development, maintaining multiple interdependent projects can become complex. Developers often face challenges like inconsistent dependencies, duplicated configurations, and difficulties in coordinating releases. Monorepos provide a solution by housing multiple projects within a single repository, simplifying dependency management, and enabling cross-package collaboration. In this article, we will explore managing full-stack monorepos using Turborepo and pnpm Workspaces, covering setup, workflow optimization, and best practices.

1. What is a Monorepo?

A monorepo is a repository that contains multiple projects, often including frontend, backend, shared libraries, and utilities. Instead of splitting code into multiple repositories, a monorepo centralizes all codebases, which offers several advantages:

2. Introducing Turborepo

Turborepo is a high-performance build system for JavaScript and TypeScript monorepos. It provides:

Using Turborepo allows teams to run only the tasks affected by changes, dramatically reducing build times.

// Example: turborepo.json configuration
{
  "$schema": "https://turborepo.org/schema.json",
  "pipeline": {
    "build": {
      "dependsOn": ["^build"],
      "outputs": ["dist/**"]
    },
    "lint": {},
    "test": {
      "dependsOn": ["build"]
    }
  }
}

3. pnpm Workspaces

pnpm Workspaces is a monorepo feature that allows multiple packages to be managed within a single repository. Key benefits include:

Setting up pnpm Workspaces involves creating a root pnpm-workspace.yaml:

# pnpm-workspace.yaml
packages:
  - 'apps/*'
  - 'packages/*'

Here, apps/* might contain your frontend and backend projects, while packages/* contains shared libraries.

4. Project Structure Example

A typical full-stack monorepo structure using Turborepo and pnpm Workspaces:

my-monorepo/
├─ apps/
│  ├─ frontend/
│  └─ backend/
├─ packages/
│  ├─ ui-library/
│  └─ utils/
├─ package.json
├─ pnpm-workspace.yaml
└─ turbo.json

This structure allows easy sharing of code between frontend, backend, and utility packages.

5. Building and Testing

With Turborepo, you can define tasks in the turbo.json to build, test, and lint projects. Turborepo intelligently runs only tasks affected by code changes:

{
  "pipeline": {
    "build": { "outputs": ["dist/**"] },
    "test": { "dependsOn": ["build"] }
  }
}

Then, run tasks using the Turborepo CLI:

# Build all projects
npx turbo run build

# Run tests for affected projects
npx turbo run test

6. Sharing Dependencies and Code

Using pnpm Workspaces, you can share dependencies between projects without duplication:

// Example: frontend/package.json
{
  "name": "frontend",
  "dependencies": {
    "react": "^18.2.0",
    "ui-library": "workspace:*"
  }
}

The workspace:* syntax ensures the local version of ui-library is used instead of fetching it from the npm registry.

“Monorepos simplify dependency management, but tooling like Turborepo and pnpm Workspaces makes it performant and scalable.”

7. Best Practices

8. Conclusion

Managing full-stack monorepos can be challenging without the right tools. Turborepo and pnpm Workspaces provide an efficient and scalable approach, enabling teams to maintain consistency, share code, and optimize build performance. By following best practices, teams can achieve faster development cycles, improved collaboration, and reliable builds across multiple projects.

Monorepos are no longer limited to large enterprises. With Turborepo and pnpm Workspaces, small to medium-sized teams can leverage monorepo advantages without sacrificing performance or developer productivity.