Migration strategy
postman-app is in the middle of a long-running architectural migration: moving code from the legacy src/renderer/ monolith into structured Nx packages.
This page explains the strategy, the current state, and exactly how to migrate a package yourself.
The problem with the monolith
src/renderer/ grew organically over many years. It has:
- ~15,700 files, mostly JavaScript (not TypeScript)
- 76 top-level directories with no enforced boundaries
- Thousands of circular imports (A imports B, B imports C, C imports A)
- No ability to test features in isolation
- A single massive webpack bundle — slow builds, no tree-shaking
You cannot fix a system like this with a big-bang rewrite. The risk of regressions is too high.
The strategy: strangler fig
The strangler fig pattern comes from botany. A strangler fig grows around a tree over many years — eventually, the fig is self-supporting and the original tree rots away.
Applied to postman-app:
- Every new feature goes into an Nx package — never into
src/renderer/ - Existing monolith code is extracted feature by feature when it needs to be touched
- Over time,
src/renderer/shrinks and the Nx packages grow
Migration states
Every Nx package has a migration tag:
| Tag | Meaning |
|---|---|
monolith-decoupled | No @postman-app-monolith/* or @@renderer/* imports. Fully independent. |
monolith-coupled | Still imports from the monolith via bridge aliases. Migration incomplete. |
| (no tag) | New package — should be decoupled from the start |
The migration process step by step
Step 1: Generate the package
Use the Nx generator — never create packages manually.
# Create a new ui-feature
npx nx generate @postman/app-generator:library --type=ui-feature --name=my-feature
# Create a new data package
npx nx generate @postman/app-generator:library --type=data --name=my-data
The generator creates the correct directory, package.json, project.json, tsconfig.json, and tags. It also updates tsconfig.json at the repo root.
Step 2: Move the code
Copy the relevant code from src/renderer/ into the new package. At this stage, use bridge imports for anything that still refers to monolith code:
// Temporary bridge — acceptable during migration phase
import { NavigationService } from '@postman-app-monolith/renderer/router/NavigationService';
Tag the package as monolith-coupled in project.json:
{
"tags": ["type:ui-feature", "scope:collaboration", "monolith-coupled"]
}
Step 3: Wire up the new package
Update the entry point in src/renderer/onboarding/src/features/ (or wherever the monolith entry is) to import from the new Nx package instead of the old path.
Step 4: Full decouple
This is a separate PR. Remove all @postman-app-monolith/* and @@renderer/* imports. For each one:
- If it is a service (like
NavigationService), inject it via dependency injection or pass it as a prop - If it is a constant, move the constant into a
libs/package - If it is a store, move the store into a
data/package
When zero bridge imports remain, change the tag to monolith-decoupled:
{
"tags": ["type:ui-feature", "scope:collaboration", "monolith-decoupled"]
}
Step 5: Update the decouple tracker
Run the refresh script and open a PR with the tracking update:
node .context/scripts/refresh-decouple-migration.js
Common blockers during decoupling
These are the patterns that make full decoupling hard:
| Blocker | Why it's hard | Solution |
|---|---|---|
NavigationService | Global singleton, tightly coupled to the router | Inject via props or React context |
| Feature flags | Injected at boot time, not importable as a module | Thread through from the entry point |
| Global singletons | AppState, GlobalStore, etc. | Wrap in a context provider at the app level |
| Transitive monolith deps | Package X imports from monolith, and Package Y imports Package X | Fix Package X first |
Current migration progress
Migration tracking is in Jira under ticket CBR-518. You can also check the state of any package by looking at its project.json tags.
# Find all still-coupled packages
grep -r "monolith-coupled" --include="project.json" -l .
# Find all fully decoupled packages
grep -r "monolith-decoupled" --include="project.json" -l .
As of version 12.8.3: approximately 25% of the codebase (by file count) has been migrated to Nx packages.
PR template for decouple PRs
All decoupling PRs should follow the template in .context/decouple-pr-description-template.md. The title format is:
[CBR-518] decouple <package-name> from monolith
And the checklist includes:
yarn validate-modulespassesyarn knipshows no new unused exportsnx lintandnx testpass on touched packages- Root
tsc -bpasses - Decouple tracker refreshed