Back to Blog
Tutorial11 min read2026-08-13

Monorepo Subdirectories — Deploying Just the apps/api Folder

Deploy a single Express API from a monorepo without splitting repos or workarounds — rootDir config, package.json detection, shared dependencies, and API deployment.

Ajay Kumar
Ajay Kumar
Founder & DevOps, PandaStack

Monorepos group related projects into one repository: a frontend in apps/web, a backend in apps/api, shared utilities in packages/common. This structure works well for teams that want atomic cross-project commits and centralized dependency management, but most deploy platforms assume one repo equals one deployable app and break when you point them at a monorepo root.

The failure mode is predictable: the platform detects multiple package.json files, picks the wrong one, runs npm install in the repo root instead of the app directory, then tries to start a server that does not exist. The build succeeds but the app never boots, and the health check times out.

PandaStack's rootDir parameter solves this by telling the build system which subdirectory contains the deployable app. The build runs inside that directory, dependencies install correctly, and the start command executes the right entry point.

Why monorepos break the default deploy path

Most platforms run the build in the repository root. They look for package.json in the top-level directory, run npm install, then execute the start command. This works perfectly for single-app repos but fails for monorepos where package.json at the root is a workspace config, not a deployable app.

For example, a Turborepo structure might look like:

my-monorepo/
├── package.json (workspace root)
├── apps/
│   ├── web/ (Next.js frontend)
│   │   └── package.json
│   └── api/ (Express backend)
│       └── package.json
└── packages/
    └── shared/
        └── package.json

Running npm install in the root sets up the workspace but does not install dependencies for apps/api. Running npm start errors because there is no start script in the root package.json. The platform needs to cd apps/api && npm install && npm start, which requires knowing the subdirectory path.

Set rootDir in pandastack.json to deploy a subdirectory

The rootDir field in pandastack.json tells the platform which directory to treat as the app root. Place the file in the repository root, then specify the relative path to the deployable app:

{
  "type": "container",
  "name": "express-api",
  "rootDir": "apps/api",
  "startCommand": "node index.js"
}

When the build runs, the system:

  1. 1Clones the entire repository
  2. 2Changes directory to apps/api
  3. 3Runs npm install (or the detected package manager)
  4. 4Executes the start command from inside apps/api

The API boots correctly because the build context is the subdirectory, not the root.

If your monorepo uses Yarn workspaces or pnpm, dependencies might hoist to the root node_modules. The build still works because the package manager resolves modules relative to the workspace root, and the platform preserves the full repository structure during the build.

Deploy via the API with rootDir in the request payload

If you are automating deploys from CI or a script, pass rootDir in the API request instead of committing pandastack.json:

curl -X POST https://api.pandastack.io/v1/projects \
  -H "Authorization: Bearer $PANDASTACK_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "slug": "container",
    "name": "express-api",
    "repositoryName": "yourname/my-monorepo",
    "branch": "main",
    "autoDeploy": true,
    "rootDir": "apps/api",
    "startCommand": "node index.js"
  }'

The platform clones the repo, builds from apps/api, and deploys the container. This approach is useful when multiple apps in the monorepo deploy to different projects and you do not want a single pandastack.json that hardcodes one path.

Add rootDir to the deploy button for template repos

If your monorepo is a starter template and you want users to deploy the API with one click, encode rootDir in the deploy button URL:

[![Deploy to PandaStack](https://dashboard.pandastack.io/deploy-button.svg)](https://dashboard.pandastack.io/deploy?repo=yourname/my-monorepo&type=container&rootDir=apps/api&name=express-api)

Query parameters:

  • repo: owner/repo
  • type: container (or static for a frontend subdirectory)
  • rootDir: relative path to the app directory
  • name: project name on the platform

When someone clicks the badge, the deploy screen pre-fills the subdirectory path. The user can override it if needed, but the default is correct, so most people just click Deploy.

Handle shared dependencies in packages/ directories

If apps/api depends on packages/shared, the imports resolve correctly as long as the workspace is configured properly. For example, with pnpm workspaces:

{
  "name": "express-api",
  "dependencies": {
    "shared": "workspace:*"
  }
}

The package manager symlinks packages/shared into apps/api/node_modules, and the Node.js module resolver finds it when the app runs require('shared') or import { foo } from 'shared'.

PandaStack preserves the full repository structure during the build, so workspace links stay intact. The only requirement is that your package.json and workspace config are valid — the platform does not modify the dependency graph.

Set different start commands per subdirectory

Monorepo apps often have different start scripts. The frontend might run next start, the API might run node src/server.js, and a worker might run node worker.js. You cannot set a single start command in the root package.json that works for all of them.

Each project gets its own startCommand in pandastack.json or the API request:

{
  "type": "container",
  "name": "express-api",
  "rootDir": "apps/api",
  "startCommand": "node src/server.js"
}

If the subdirectory's package.json has a start script, the platform auto-detects it and you do not need to override startCommand. But if the script is named differently or you want to pass command-line flags, specify it explicitly.

Debug "Cannot find module" errors after deploy

If your app builds successfully but crashes at runtime with Cannot find module 'some-package', the dependency is missing from apps/api/package.json even though it works locally. This happens when a package is installed in the root node_modules and Node resolves it via hoisting, but the dependency is not declared in the subdirectory's package.json.

The fix is to add the dependency to apps/api/package.json:

cd apps/api
npm install some-package

Commit the change and redeploy. The package installs into the subdirectory's dependencies, and the app boots correctly.

Deploy multiple apps from the same monorepo

If you want to deploy both apps/web and apps/api, create two separate projects on the platform, each with a different rootDir:

Project 1 (frontend):

curl -X POST https://api.pandastack.io/v1/projects \
  -H "Authorization: Bearer $PANDASTACK_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "slug": "static",
    "name": "web",
    "repositoryName": "yourname/my-monorepo",
    "branch": "main",
    "rootDir": "apps/web",
    "buildCommand": "npm run build",
    "outputDir": "dist"
  }'

Project 2 (backend):

curl -X POST https://api.pandastack.io/v1/projects \
  -H "Authorization: Bearer $PANDASTACK_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "slug": "container",
    "name": "api",
    "repositoryName": "yourname/my-monorepo",
    "branch": "main",
    "rootDir": "apps/api",
    "startCommand": "node src/server.js"
  }'

Both projects watch the same repository. When you push a commit, PandaStack detects which apps changed by diffing the file paths and only rebuilds the affected ones. If you edit a file in apps/api, only the API redeploys; the frontend stays untouched.

Set environment variables per app

Each project gets its own set of environment variables. The frontend might need VITE_API_URL, the backend might need DATABASE_URL. You configure them separately in each project's settings or via the API:

panda projects env <api-project-id> --set DATABASE_URL=postgres://...
panda projects env <web-project-id> --set VITE_API_URL=https://api.example.com

The variables are scoped to the project, so there is no risk of the frontend accidentally inheriting the database credentials or the backend getting the wrong API URL.

References

  • [Express deployment best practices](https://expressjs.com/en/advanced/best-practice-performance.html)
  • [PandaStack monorepo guide](https://docs.pandastack.io/projects/)
  • [Turborepo documentation](https://turbo.build/repo/docs)

Ready to deploy?

Start free on PandaStack.

Start free on PandaStack

More in Tutorial

Browse all Tutorial articles →

See also