Skip to main content

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:

MetricValue
Total Nx packages253
Fully decoupled from monolith160 (63%)
Still coupled94 (37%)
Files in src/renderer/~19,000
Files in Nx packages~7,900
Pre-integration CI time7–11 minutes
Build time (Rspack)~5 seconds
Production release cadenceDaily (automated)
Active product teams25+

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

Area20202026
LanguageJavaScriptTypeScript strict (new), JS (legacy)
Build toolWebpack 4Rspack + SWC
Build time29 seconds5 seconds
Package managerYarn 1Yarn 4
Node version1224
CI systemCircleCIGitHub Actions
Monorepo toolNoneNx
State managementMobXZustand + React Query
Unit testingJestVitest (migrating)
E2E testingWDIOPlaywright
Release cadenceWeeklyDaily (automated)
Products1 (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: