M Logo
๐Ÿ“ TypeScript from Zero ยท Part 49 of 50

TypeScript Project References: composite, tsc -b and Shared Types for CLI and API

November 6, 2026

Welcome to episode 49 of TypeScript from Zero, a series for complete beginners (no JavaScript needed). Welcome back to TypeScript from zero! Our task tracker has a command line tool and an HTTP server. Both need the same types. Today we split the code into three packages, a shared one, a cli and an api, and we teach tsc how they depend on each other. The tool for that is project references. We are in a new folder called mono. Every code block below is the exact code and output from the video, ready to copy.

Setup

We start in the terminal and open the project in VS Code:

bash
cd mono
code .

1. The folder

The folder is prepared. It is an npm workspace: three packages under packages, and npm links them, so that one package can import another by its name, at tasks slash shared. Every package has its own T S config file, and they all extend this base file. Look at the option composite. It marks a project as one that other projects may depend on. It needs declaration files, which we also switch on.

2. Shared

The shared package holds the task type, and a sample value. The cli package imports it by name, and prints the title.

3. Problem

Now we build the cli, with tsc dash b, for build mode. And it fails: cannot find module tasks slash shared. TS2307. The shared package has not been built yet, so there are no declaration files, and tsc has nothing to read.

packages/cli/src/main.ts:

ts
import { sample } from "@tasks/shared";
console.log("cli:", sample.title);

packages/shared/src/task.ts:

ts
export type Task = { id: number; title: string };
export const sample: Task = { id: 1, title: "Buy milk" };
text
$ npx tsc -b packages/cli
packages/cli/src/main.ts(1,24): error TS2307: Cannot find module '@tasks/shared' or its corresponding type declarations.

4. references

The cli must say that it depends on shared. In its T S config file we add references, with the path to the shared package. Now build mode knows the order: shared first, then the cli. It builds both with one command. In the shared folder, a dist folder appears, with the declaration file that the cli needed.

packages/cli/tsconfig.json:

ts
{
  "extends": "../../tsconfig.base.json",
  "compilerOptions": { "rootDir": "./src", "outDir": "./dist" },
  "include": ["src"],
  "references": [{ "path": "../shared" }]
}
bash
$ npx tsc -b packages/cli
text
$ ls packages/shared/dist
task.d.ts
task.js

5. The api

The api package gets the same treatment: a source file, and the reference. Then one command builds everything that is needed. And both programs run, from their dist folders, with the same shared type.

packages/api/src/server.ts:

ts
import { sample } from "@tasks/shared";
console.log("api:", sample.id);

packages/api/tsconfig.json:

ts
{
  "extends": "../../tsconfig.base.json",
  "compilerOptions": { "rootDir": "./src", "outDir": "./dist" },
  "include": ["src"],
  "references": [{ "path": "../shared" }]
}
bash
$ npx tsc -b packages/cli packages/api
text
$ node packages/cli/dist/main.js
cli: Buy milk
text
$ node packages/api/dist/server.js
api: 1

6. Incremental

Build mode is incremental. We run it again with -- verbose, and it tells us that every project is up to date, because no input is newer than its output. Nothing is built twice. In a big code base this saves minutes. And when the shared types change, only the packages that depend on them are rebuilt.

text
$ npx tsc -b --verbose packages/cli packages/api
04:15:43 PM - Projects in this build: 
    * packages/shared/tsconfig.json
    * packages/cli/tsconfig.json
    * packages/api/tsconfig.json

04:15:43 PM - Project 'packages/shared/tsconfig.json' is up to date because newest input 'packages/shared/src/task.ts' is older than output 'packages/shared/tsconfig.tsbuildinfo'

04:15:43 PM - Project 'packages/cli/tsconfig.json' is up to date because newest input 'packages/cli/src/main.ts' is older than output 'packages/cli/tsconfig.tsbuildinfo'

04:15:43 PM - Project 'packages/api/tsconfig.json' is up to date because newest input 'packages/api/src/server.ts' is older than output 'packages/api/tsconfig.tsbuildinfo'

Recap

Today: composite projects, references between them, build mode with tsc dash b, and incremental builds. Next, and last: publishing a typed package, and a look back at the whole series.

ยฉ 2026 All Rights Reserved By Mahmudul Alam