Monorepo Setup & Code Ownership

0/3 in this phase0/35 across the roadmap

📖 Concept

Monorepos allow you to manage multiple packages (shared UI kit, API client, analytics, feature modules) in a single repository with shared tooling and atomic commits.

When to use a monorepo for React Native:

  • You have a shared component library used across multiple apps
  • You want to share code between RN mobile and React web
  • You have independent teams that need clear package boundaries
  • You want atomic changes across multiple packages (update an API + consumers in one PR)

Popular monorepo tools:

  • Turborepo — Incremental builds, remote caching, simple config
  • Nx — More features, plugin ecosystem, affected-based testing
  • Yarn/npm workspaces — Basic workspace management, dependency hoisting

Code ownership strategy: Code ownership defines WHO is responsible for WHAT code. At scale, this prevents:

  • "Tragedy of the commons" — nobody owns shared code, quality degrades
  • Bottleneck reviews — one person reviews all PRs
  • Unreviewed changes to critical infrastructure

CODEOWNERS file pattern:

# .github/CODEOWNERS
# Platform team owns core infrastructure
/packages/platform/**        @platform-team
/packages/design-system/**   @design-system-team

# Feature teams own their features
/apps/mobile/src/features/checkout/**   @payments-team
/apps/mobile/src/features/feed/**       @feed-team
/apps/mobile/src/features/auth/**       @auth-team

# Shared packages need platform team review
/packages/api-client/**      @platform-team
/packages/analytics/**       @platform-team @data-team

💻 Code Example

codeTap to expand ⛶
1// === MONOREPO STRUCTURE ===
2
3/*
4my-app/
5├── apps/
6│ ├── mobile/ ← React Native app
7│ │ ├── src/
8│ │ ├── android/
9│ │ ├── ios/
10│ │ └── package.json
11│ └── web/ ← React web app (optional)
12│ ├── src/
13│ └── package.json
14├── packages/
15│ ├── ui-kit/ ← Shared components
16│ │ ├── src/
17│ │ │ ├── Button/
18│ │ │ ├── Input/
19│ │ │ └── index.ts
20│ │ ├── package.json
21│ │ └── tsconfig.json
22│ ├── api-client/ ← Shared API layer
23│ │ ├── src/
24│ │ │ ├── client.ts
25│ │ │ ├── endpoints.ts
26│ │ │ └── types.ts
27│ │ └── package.json
28│ ├── shared-types/ ← Shared TypeScript types
29│ │ ├── src/
30│ │ └── package.json
31│ └── analytics/ ← Shared analytics
32│ ├── src/
33│ └── package.json
34├── turbo.json ← Turborepo config
35├── package.json ← Root workspace config
36└── tsconfig.base.json ← Shared TS config
37*/
38
39// turbo.json — Build pipeline
40const turboConfig = {
41 "$schema": "https://turbo.build/schema.json",
42 "pipeline": {
43 "build": {
44 "dependsOn": ["^build"], // Build dependencies first
45 "outputs": ["dist/**"]
46 },
47 "test": {
48 "dependsOn": ["build"],
49 "outputs": []
50 },
51 "lint": {
52 "outputs": []
53 },
54 "dev": {
55 "cache": false,
56 "persistent": true
57 }
58 }
59};
60
61// packages/api-client/src/client.ts — Shared across mobile + web
62import type { APIResponse, RequestConfig } from '@myapp/shared-types';
63
64export class APIClient {
65 constructor(
66 private baseURL: string,
67 private getToken: () => Promise<string | null>,
68 ) {}
69
70 async request<T>(config: RequestConfig): Promise<APIResponse<T>> {
71 const token = await this.getToken();
72
73 const response = await fetch(`${this.baseURL}${config.endpoint}`, {
74 method: config.method,
75 headers: {
76 'Content-Type': 'application/json',
77 ...(token ? { Authorization: `Bearer ${token}` } : {}),
78 ...config.headers,
79 },
80 body: config.body ? JSON.stringify(config.body) : undefined,
81 });
82
83 if (!response.ok) {
84 throw new APIError(response.status, await response.text());
85 }
86
87 return response.json();
88 }
89}
90
91// apps/mobile/src/config/api.ts — Mobile-specific setup
92import { APIClient } from '@myapp/api-client';
93import { getAuthToken } from '../features/auth';
94
95export const apiClient = new APIClient(
96 'https://api.example.com/v1',
97 getAuthToken,
98);

🏋️ Practice Exercise

Monorepo & Ownership Exercises:

  1. Set up a Turborepo monorepo with a React Native app and a shared UI package
  2. Create a shared TypeScript types package used by both mobile app and API client
  3. Set up a CODEOWNERS file for a 3-team organization
  4. Configure ESLint import restrictions to enforce package boundaries
  5. Implement a shared analytics package with platform-specific adapters (mobile/web)
  6. Set up CI that only runs tests for packages affected by a PR (using Turborepo or Nx affected)

⚠️ Common Mistakes

  • Not configuring Metro bundler correctly for monorepo — Metro needs explicit watchFolders for packages outside the app directory

  • Circular dependencies between packages — shared-types imports from api-client which imports from shared-types

  • Not defining clear package boundaries — teams start importing internal files from other packages instead of the public API

  • Making everything a shared package — only share when 2+ consumers exist; premature abstraction adds complexity

  • Not setting up consistent tooling (ESLint, TypeScript, Jest configs) at the root level — each package evolves its own config

💼 Interview Questions

🎤 Mock Interview

Mock interview is powered by AI for Monorepo Setup & Code Ownership. Login to unlock this feature.