The architecture of postman-app
How a Chrome extension became one of the world's most-used developer tools — and the engineering decisions that shaped every line of code you'll write here.
Act 1 — A simple beginning (2012–2018)
In 2012, Abhinav Asthana built Postman as a Chrome extension in his spare time. The entire codebase was a single JavaScript file. It did one thing: let developers send HTTP requests and see the responses. Clean. Simple. Useful.
Two years later, in 2014, the team made a decision that would define the next decade of engineering: they rebuilt Postman as a desktop application using Electron — a framework that lets you ship a web app as a native desktop app. This was a bold bet. Electron let web developers build desktop software without learning Objective-C or Win32. The entire team could contribute. No platform silos.
The codebase had one home: src/renderer/. One folder. One team. Everything in one place. And for a while, that was fine. The team was small, everyone knew the code, and features shipped fast.
Act 2 — Growth, and the weight it brings (2018–2022)
Then Postman grew. Fast.
Millions of developers started using it daily. Hundreds of integrations were built. The team scaled from a handful of engineers to hundreds. The product expanded from "send HTTP requests" to API design, documentation, monitoring, testing, collaboration, and more.
And through all of it, every new feature went into the same folder: src/renderer/.
By the early 2020s, src/renderer/ had 19,000+ files. All JavaScript. No TypeScript for most of it. No rules about what could import from what. A change to the billing sidebar could break the collections view. A tweak to the environment editor might silently break the request runner. Nobody was sure. Nobody could be sure.
Patrick Sevat, who joined to lead the Web Platform team, described what he found as a "sticky, magnetic bowl of spaghetti."
The numbers told the story:
- 567 circular dependencies — A imports B, B imports C, C imports A. The dependency graph was not a tree. It was a knot.
- TypeScript's language server (
tsserver) is capped at 4GB of RAM. The monolith alone consumed ~2GB. Opening a single file pushed the IDE over the limit. Engineers were restarting their TypeScript server several times a day just to get basic features like "go to definition" working. - Every PR ran the entire test suite — 30+ minutes, regardless of what you changed. A typo fix in a tooltip waited the same amount of time as a core platform change.
- 25+ teams were all making changes in the same directory with no boundaries. The probability of someone's change breaking someone else's feature was not a bug — it was a structural guarantee.
And then came a new requirement that made all of this urgent: Postman needed to become multiple products.
The company was building Postman Black — a self-hosted enterprise version for customers who couldn't send data through Postman's cloud. They wanted a VS Code extension. There was a browser version to ship. And the request runner needed to become an open-source CLI agent.
Each of these products needed pieces of the Postman codebase. But you couldn't take a single file from src/renderer/ without pulling in the entire monolith. The spaghetti was so tangled that nothing could be extracted.
Something had to change.
Act 3 — First steps (2022)
Before tackling the architecture, the team stabilized the foundation. Build times went from 29 seconds (Webpack 4) to 5 seconds (Rspack + SWC). CI moved from CircleCI to GitHub Actions. Releases went from weekly to daily.
These were real wins — but they didn't fix the structural problem. The spaghetti was still there. The IDE still crashed. Postman Black still couldn't share any code with the main app. Tooling improvements are ceiling-limited by architecture. At some point, you have to fix the thing itself.
Act 4 — The architecture revolution (2022–present)
In 2022, the team made the architectural decision that defines how postman-app is built today: adopt Nx as a monorepo management tool and introduce a strict layer system with enforced module boundaries.
The insight was this: the problem with src/renderer/ wasn't that it was large. Large codebases can be fine. The problem was that it had no rules. No boundaries. No enforced separation of concerns. Everything could import everything, so eventually everything imported everything.
The solution: create a taxonomy of module types, each with a clear purpose and strict rules about what it can depend on.
The one rule that changed everything: dependencies only flow downward. A layer can import from layers below it. Never from layers above. An ESLint rule enforces this on every commit. Violations fail CI.
This sounds simple. It is simple. But simple rules consistently enforced create predictable systems. And predictable systems are what allow hundreds of engineers to work in the same codebase without constantly breaking each other.
What each layer actually means
Think of it like a city. The libs/ layer is the raw materials — concrete, steel, glass. Generic. Reusable anywhere. No city-specific customization.
platform-libs/ is the infrastructure — electricity, water, internet. Every building uses it. Nobody thinks about it while it's working. The translation system (i18n-sdk), the API client (gateway-service), real-time events (realtime-pubsub), analytics — all horizontal services that every feature team consumes.
data/ is the plumbing inside each building — the pipes and wiring specific to each unit. Zustand stores, React Query hooks, API clients. A data package knows everything about its domain but has zero knowledge of what UI renders it.
ui-features/ is the furniture — self-contained, async-loaded feature modules that each own a complete slice of functionality. The collections sidebar, the AI chat, the command palette. These are where most new development happens. They're lazy-loaded so the app starts fast regardless of how many features exist.
views/ is the rooms — URL-addressable screens that the router navigates to. Their job is to orchestrate which ui-features to show and handle navigation logic.
The migration strategy: strangler fig
Moving 19,000 files overnight is impossible. Risky doesn't begin to cover it.
The team chose the strangler fig pattern — named after a vine that grows around a tree until the tree is no longer needed. The original tree doesn't disappear immediately. The fig grows slowly, wrapping around it, taking over its structure piece by piece, until one day the tree has rotted away and the fig stands on its own.
In practice: every new feature goes into an Nx package. When old code needs to change, it's extracted into an Nx package first. A temporary bridge (@postman-app-monolith/* import alias) lets new packages still reference old code during the transition. Then a follow-up PR removes the bridge. The package is tagged monolith-decoupled. The monolith shrinks by a little.
One package at a time. One PR at a time. Since 2022.
Act 5 — Where we are today
The numbers as of 2026:
| Metric | Value |
|---|---|
| Total Nx packages | 253 |
| Fully decoupled from monolith | 160 (63%) |
| Still coupled | 94 (37%) |
Files in src/renderer/ | ~19,000 |
| Files in Nx packages | ~7,900 |
| Pre-integration CI time | 7–11 minutes |
| Build time (Rspack) | ~5 seconds |
| Production release cadence | Daily (automated) |
| Active product teams | 25+ |
The transformation is real but incomplete. The two worlds still coexist:
src/renderer/ is still the production entry point — the old tree is still standing. But it is shrinking, and the new architecture around it is growing stronger every week.
The technical stack, then and now
| Area | 2020 | 2026 |
|---|---|---|
| Language | JavaScript | TypeScript strict (new), JS (legacy) |
| Build tool | Webpack 4 | Rspack + SWC |
| Build time | 29 seconds | 5 seconds |
| Package manager | Yarn 1 | Yarn 4 |
| Node version | 12 | 24 |
| CI system | CircleCI | GitHub Actions |
| Monorepo tool | None | Nx |
| State management | MobX | Zustand + React Query |
| Unit testing | Jest | Vitest (migrating) |
| E2E testing | WDIO | Playwright |
| Release cadence | Weekly | Daily (automated) |
| Products | 1 (desktop) | 4 (desktop, web, Black, VS Code) |
What this means for you
If you're joining the team and reading this: you're here at an interesting moment. The architecture is mid-transformation. Some of the code you'll work in is modern, clean, and well-structured. Some of it is the old monolith, shaped by the pressures of fast growth and accumulated decisions.
Your job is not to work around the architecture — it's to move it forward. Every new feature you build in Nx packages rather than src/renderer/ is part of the migration. Every test you write makes the codebase more trustworthy. Every package you decouple makes the whole system better.
The strangler fig grows one branch at a time.
References
This page synthesizes content from:
- Vision for Postman App Development — Patrick Sevat
- Nx libs & module boundaries — Patrick Sevat
- Why module boundaries for libraries? — Patrick Sevat
- Plan and vision for CI for 2026 — Opi Danihelka
- Nx Monolith Decoupling — Decision Record — Sachin Lohani
- Decoupling — Packages (live backlog) — Sachin Lohani