Mohit Shrivastava logoMOHIT SHRIVASTAVAtechnology leader · builder · operatormohit@portfolio ~/careerdownload-cv
TERMINAL · case-studies / cms-decoupled-graphql-gateway
mohit@portfolio ~/careercat cms-decoupled-graphql-gateway.md
Media platformsInfra & performance

CMS-Decoupled GraphQL Gateway

A standalone GraphQL service that reads straight from MySQL replicas and bypasses WordPress entirely — turning ~700ms, error-prone article fetches into sub-10ms reads, without changing a single frontend query.

Built & validated against production data — production-ready
$ diff — read path: before → after
-Next.js → WordPress (PHP) → WPGraphQL → MySQL
-SQL_CALC_FOUND_ROWS scans 1.4M rows · ~6.3s avg
-503 / 502 storms · silent empty pages cached 25 min
+Next.js → CMS-Decoupled Gateway → MySQL replica (private VPC)
+Lean LIMIT / OFFSET reads · sub-10ms
+Identical WPGraphQL response shape · 112 tests green
$ cat cms-decoupled-graphql-gateway.md

01 · Everything talks to WordPress

The website, the frontend, the mobile apps — everyone read their data through WordPress and its WPGraphQL plugin. Node.js speaks JavaScript; WordPress speaks PHP. Two different worlds, forced to talk to each other, and the friction showed up as dead stories, recurring 503 server errors, and a bottleneck nobody could fully explain.

Under the hood, WordPress's pagination used SQL_CALC_FOUND_ROWS — which forces MySQL to count the entire table on every paginated query. Against 696,000+ articles, that meant scanning roughly 1.4 million rows to return a handful of posts, averaging about 6.3 seconds. When PHP timed out, the frontend silently received empty pages, and Cloudflare cached the broken result for up to 25 minutes.

The content was in the database the whole time. PHP was the blockage.

02 · The idea that sat with me for six months

This was in my mind for almost half a year — that there should be something in between; a wall between the frontend and the CMS that talks directly to the database. I never had the shape of it.

Then it struck: build a backend that speaks to the read replicas directly and answers in the exact GraphQL the frontend already expects. Take the same payloads, return the same response — but cut WordPress and PHP out of the read path entirely.

The content was in the database the whole time. PHP was the blockage.

03 · What I built

A standalone Node.js / TypeScript service (graphql-yoga) that reads directly from the MySQL read replicas over a private VPC, bypassing PHP and WordPress completely. It is an API gateway, a content gateway, and a routing layer in one.

It accepts the same GraphQL queries the frontend already sends and returns byte-for-byte the same response shape. So the frontend switches over by changing a single environment variable — no rewrite, no new query contracts. It is stateless and strictly read-only by design; WordPress stays exactly where it is, and editors keep using it daily.

04 · How it works

WordPress keeps writing to the primary database; five read replicas serve reads. Instead of PHP booting 30+ plugins and running SQL_CALC_FOUND_ROWS, the gateway runs lean LIMIT/OFFSET queries straight against a replica.

The hard part wasn't the speed — it was fidelity. WordPress stores everything in an EAV model: a single featured image is a three-hop lookup, an article's URL isn't stored anywhere and has to be reconstructed, and half the fields are PHP-serialized. I reverse-engineered exactly what WPGraphQL returns and matched it across every query the frontend makes, backed by 112 passing tests.

05 · What changed

Reads went from roughly 600–700ms — and frequently dying — to under 10 milliseconds. The 503/502 storms and silently-broken pages disappear in validation. Same data, same shape, a fraction of the cost, and none of the fragility.

Because cutover is a single environment variable, rollback is instant. That made a deep architectural change safe to put in front of a national news platform.

06 · Why it matters beyond FMT

Almost every news platform runs on WordPress and hits this exact wall: PHP-bound reads, GraphQL-plugin overhead, pages that die under traffic. The name says the value out loud — decouple the read path from the CMS, talk to the database directly, and leave the editorial experience untouched.

It's not a media trick. It's a read-architecture any CMS-heavy business can adopt — which is exactly why I built it as a portable pattern, not a one-off patch.

— · Ownership & proof

Solo: architecture, code, infrastructure, validation
112 tests passing against production data
Zero frontend query changes — one env var to cut over
Built & validated, production-ready