Dependency rules
The most critical architectural rule in postman-app is the unidirectional dependency graph: dependencies flow downward only. A layer can import from layers below it, but never from layers above.
These rules are not just conventions — they are enforced by the build. Violating them causes a lint error that fails CI.
The rule at a glance
apps/ → can import anything below
views/ → can import ui-features, data, platform-*, libs
ui-features/ → can import data, platform-*, libs, other ui-features*
data/ → can import platform-libs, libs
platform-ui/ → can import platform-libs, libs
platform-libs/ → can import libs only
libs/ → can import external npm only
*ui-features importing other ui-features is allowed but must be done carefully to avoid circular dependencies. Prefer keeping ui-features independent.
Why these rules exist
Without enforced boundaries, code in a large codebase eventually develops circular dependencies — A imports B, B imports C, C imports A. This causes:
- Unpredictable initialization order (runtime errors)
- Inability to tree-shake unused code (bloated bundles)
- Tests that are difficult to isolate
- Impossible to extract packages into separate repos later
The monolith (src/renderer/) has no enforced boundaries and has accumulated thousands of circular imports over the years. The Nx layer system is the fix.
Correct examples
// ✅ ui-features/collections-sidebar importing from data/
import { CollectionStore } from '@postman-app/collection-data';
// ✅ data/ importing from platform-libs/
import { gatewayClient } from '@postman-app/gateway-service';
// ✅ platform-libs/ importing from libs/
import { retryFetch } from '@postman-app/fetch-with-retry';
// ✅ views/ importing from ui-features/
import { CollectionsSidebar } from '@postman-app/collections-sidebar';
Incorrect examples
// ❌ data/ importing from ui-features/ — data never knows about UI
import { something } from '@postman-app/collections-sidebar';
// ❌ libs/ importing from platform-libs/ — libs has no app-specific deps
import { i18n } from '@postman-app/i18n-sdk';
// ❌ ui-features/ importing from views/ — features don't know about routing
import { WorkspaceOverview } from '@postman-app/workspace-overview';
// ❌ platform-libs/ importing from data/ — infra doesn't know about domain stores
import { workspaceStore } from '@postman-app/workspace-data';
The monolith exception
src/renderer/ code does not follow these rules — it predates them. If you are extracting code from the monolith into an Nx package:
- Initially you may use the
@postman-app-monolith/*import alias to bridge the gap - The package is tagged
monolith-coupledin its Nx tags - A follow-up PR removes the monolith imports and re-tags the package
monolith-decoupled
See Migration strategy for the full process.
Import aliases reference
| Alias | Resolves to | When to use |
|---|---|---|
@postman-app/<name> | Nx package via node_modules symlink | All new code — canonical import path |
@postman-app-monolith/* | src/* | Temporary bridge for packages mid-migration |
@@renderer/* | src/renderer/* | Legacy only — do not use in new code |
How the rules are enforced
Rules are configured in each package's project.json under the tags field:
{
"tags": ["type:ui-feature", "scope:collaboration", "monolith-decoupled"]
}
The ESLint rule @nx/enforce-module-boundaries reads these tags and applies the constraint matrix defined in .eslintrc.js at the repo root. Running nx lint <package> will show boundary violations.
Checking for violations
# Check a specific package
nx lint workspace-overview
# Check all packages
nx run-many --target=lint --all
# Find all existing boundary violations
yarn knip