Frontend
GraphQL
A typed graph over your data, built by engineers who know when REST would serve you better.
Overview
GraphQL is a query language and runtime for APIs. Instead of a REST API’s many endpoints, each returning a fixed shape, GraphQL exposes a single endpoint and lets the client ask for exactly the fields it needs in one request. The server describes everything it can return as a strongly-typed schema, and the client sends a query that mirrors the shape of the data it wants back. That inversion — the client stating its needs rather than the server dictating them — is the whole point, and it is what solves REST’s two chronic irritations: over-fetching, where an endpoint hands you far more than the screen uses, and under-fetching, where one screen needs three round trips to three endpoints to assemble what it shows.
The schema is the part that matters most. It is a genuine contract between front end and back end: every type, field, and argument is declared, so the front end knows precisely what it can request and the back end knows precisely what it must return. That contract is introspectable, which is why GraphQL tooling feels the way it does — GraphiQL and its descendants give you autocomplete, inline documentation, and a live query explorer generated straight from the schema, and code generators turn the schema into fully typed client code so a mistyped field fails at compile time rather than in production. Underneath, each field is backed by a resolver, a small function that knows how to fetch that piece of data, and the runtime walks the query resolving fields as it goes. Beyond queries you get mutations for writes and subscriptions for real-time updates pushed over a persistent connection.
We reach for GraphQL when a system has many clients that each need different shapes of the same data — a web app, an iOS app, an Android app, a partner integration — or when a screen has to stitch together several back-end services and you would rather do that composition once, in a typed graph, than in every client. Federation extends this: several independently-owned services each publish a slice of the schema, and a gateway composes them into one graph the client sees as a whole. But we are equally blunt about the cost. GraphQL is over-adopted, chosen as a default when it should be chosen for a reason, and its trade-offs are real: caching, rate-limiting, and operational complexity all get harder in exchange for the flexibility. When those costs outweigh the benefit, we say so and build REST instead.
Best for — Multi-client and aggregation-heavy systems — mobile plus web plus partners over shared data — where a typed graph pays for its complexity, and where a senior team can make the honest call on whether REST would serve you better.
Why teams choose GraphQL
Clients fetch exactly what they need
One request returns precisely the fields a screen uses — no over-fetching bloat, no under-fetching round trips. On mobile networks especially, sending less data and making fewer requests is a measurable win, not a theoretical one.
The schema is a living contract
A single strongly-typed schema binds front end and back end. Front-end teams build against a known shape, code generation turns that shape into typed client code, and whole categories of integration bug — wrong field name, wrong type, missing field — fail at build time instead of in production.
One graph over many sources
GraphQL composes data from several databases, services, and third-party APIs behind one endpoint. Clients see a coherent graph; the messy fan-out to underlying systems stays server-side where it belongs, and — with federation — each team owns its slice of the schema.
Why businesses choose GraphQL
- You serve several clients with divergent data needs and are tired of either bloating one REST endpoint or maintaining a bespoke endpoint per screen.
- Your screens aggregate many services, and you want that composition done once, server-side, in a typed graph rather than repeated in every client.
- You value a schema that documents itself and generates typed client code, so large front-end and back-end teams integrate without stale docs or guesswork.
- You want a senior team that will build GraphQL properly — batching, depth-limiting, sane caching — and will tell you honestly if REST is the simpler, cheaper fit.
What we build with GraphQL
The capabilities this technology is genuinely strong at — and what we most often build with it.
Schema-first design
We design the schema before writing resolvers — types, fields, arguments, interfaces, unions, and enums modelled around how clients actually consume the graph, not around your table layout. The schema is the contract, so we treat it as a deliberate design artefact and version it carefully, because a graph that leaks your database structure ages badly.
Resolvers with DataLoader batching
Every resolver that touches a data source is written with the N+1 problem in mind. We use DataLoader-style batching and caching so a query over a list of a hundred items issues a handful of batched database calls, not a hundred sequential ones. This is the single most important discipline in a performant GraphQL server, and it is not optional.
Queries, mutations, and subscriptions
Reads via queries, writes via mutations with explicit input types and structured payloads, and real-time updates via subscriptions over WebSockets or server-sent events. We design mutations to return the changed data the client needs so caches update cleanly, and we use subscriptions only where genuine push matters rather than as a default.
Introspection and self-documenting tooling
The schema is introspectable, so GraphiQL and Apollo Sandbox give your engineers a live, documented query explorer with autocomplete out of the box. Paired with code generation, the same schema produces fully typed hooks and clients, so the front end’s calls are checked against the graph at compile time.
Federation and schema composition
For larger organisations we compose multiple subgraphs — each owned by a different team or service — into one supergraph behind a gateway. Clients query a single unified graph; ownership stays distributed. We use federation where team boundaries justify it, not as architecture theatre on a system that a single schema would serve fine.
Query cost and depth protection
Because clients write their own queries, we defend the server deliberately: depth limiting to reject absurdly nested queries, cost analysis to reject expensive ones, persisted queries so production only accepts a known allow-list, and rate limiting keyed to query cost rather than raw request count. A public GraphQL endpoint without these is an outage waiting to happen.
Use cases
Mobile and web from one graph
A product with an iOS app, an Android app, and a web front end, each needing a different subset of the same data. One schema serves all three, each client fetches exactly its shape, and mobile stops paying for fields it never renders.
Back-end-for-frontend aggregation
A GraphQL layer that stitches together several internal microservices and third-party APIs into one graph, so a rich screen makes a single query instead of orchestrating six REST calls with all the waterfall and error-handling pain that implies.
Federated graph across teams
A larger organisation where separate teams own users, billing, catalogue, and orders as independent subgraphs, composed by a gateway into one supergraph — distributed ownership, unified client experience, no shared monolith.
Real-time collaborative and live features
Dashboards, notifications, chat, and live status that need server push, built on subscriptions alongside the same queries and mutations, within one type system rather than a separate real-time stack bolted on the side.
When GraphQL is the right choice
- Right when you have several different clients — web, mobile, partners — consuming the same data but each needing a different shape of it. Letting each client request exactly its fields, from one endpoint, is GraphQL at its strongest.
- Right when a typical screen aggregates data from many sources or services, and you would otherwise force clients into a waterfall of REST calls. GraphQL collapses that into a single typed query and does the fan-out server-side.
- Right when the schema-as-contract genuinely pays off: large front-end and back-end teams working in parallel, where introspection, generated types, and self-documenting tooling remove a whole class of integration friction and stale-documentation drift.
- Wrong for simple CRUD behind a single, well-known client — an internal admin panel, a straightforward web app talking to its own back end. The schema, resolvers, and tooling are overhead you pay for a flexibility you are not using; a handful of REST endpoints is simpler to build and run.
- Wrong for public, cacheable, high-read APIs where HTTP caching is the performance strategy. REST’s URL-per-resource model lets CDNs and browsers cache trivially; GraphQL’s single POST endpoint throws that away and forces you to rebuild caching by hand. If cache-ability is the point, REST is the better tool.
GraphQL: pros and cons
Strengths
- Clients request exactly the fields they need from a single endpoint, ending both the over-fetching and under-fetching that dog REST across multiple clients.
- The strongly-typed, introspectable schema is a real contract: self-documenting tooling, autocomplete, and end-to-end generated types catch integration errors before they ship.
- One query can aggregate many back-end sources, and federation lets independent teams compose their services into a single graph without a monolith.
- Subscriptions give first-class real-time updates over a persistent connection, alongside queries and mutations, within the same schema and type system.
Trade-offs
- HTTP caching is much harder than with REST. There is no URL per resource, so CDNs and browser caches cannot help you out of the box — you rebuild caching with persisted queries and client-side normalised caches, and it is more work for less reach.
- The N+1 query problem is built in: naive resolvers fire one database query per item in a list. Avoiding it demands DataLoader-style batching discipline on every resolver that touches a data source, and forgetting once quietly wrecks performance.
- A single flexible endpoint is a single attack surface. Deeply nested or deliberately expensive queries can exhaust the server, so you must add query-depth limits, cost analysis, and rate control that REST gets more simply per endpoint.
- Server-side complexity is higher across the board, and the rough edges are real: file uploads are clunkier than a plain multipart POST, and error handling — partial successes, errors nested in the response rather than in HTTP status codes — is fiddlier than REST’s status-code model.
Architecture
A GraphQL server is three layers we keep deliberately separate: the schema that defines the contract, the resolvers that fetch data, and the data-access layer underneath. The mistake we see most often is resolvers reaching straight into the database with the query the client happened to send, which welds your API shape to your table shape and makes both hard to change. We put a service or repository layer between resolvers and data, so the schema can be designed for clients while the storage evolves independently.
GraphQL rarely stands alone. It usually sits as a layer over existing REST services, databases, and third-party APIs, doing the aggregation clients would otherwise do themselves. We are explicit about where it belongs: as a back-end-for-frontend gateway, or as a federated supergraph when multiple teams each own a slice. That federation decision is an organisational one as much as a technical one — it is worth the gateway and subgraph machinery when team boundaries are real, and pure overhead when one team could own a single schema. We make that call on the shape of your organisation, not on fashion.
Performance
GraphQL’s defining performance risk is the N+1 problem. A query over a list resolves each item’s nested fields independently, and a naive server turns one logical request into hundreds of database round trips. We solve it the only way that actually works: DataLoader-style batching and per-request caching on every resolver that hits a data source, so those hundreds of calls collapse into a few batched ones. This is designed in from the first resolver, because retrofitting it across a mature graph is painful.
Caching is the other honest constraint. GraphQL forfeits the free HTTP and CDN caching that REST gets from having a URL per resource, because everything is a POST to one endpoint. We recover what we can with a normalised client-side cache — Apollo or urql — that dedupes and reuses data across queries, and with persisted queries that let a GET-able, cacheable request stand in for a known operation. We also enforce query cost limits so a single expensive query cannot degrade the service for everyone. We will tell you plainly when your read patterns are so cache-friendly that REST would simply be faster.
Security
A single endpoint where clients compose their own queries is a different security posture from REST, and it needs deliberate defence. The headline risk is resource exhaustion through complex queries — deep nesting, wide fan-out, or maliciously expensive shapes. We mitigate with query-depth limiting, cost analysis that scores a query before executing it, timeouts, and — for production traffic we control — persisted queries, so the server only accepts an allow-listed set of known operations rather than arbitrary client input.
Authorisation deserves particular care because GraphQL’s graph structure invites clients to traverse from one object to related ones. Access control belongs in the resolver and data layer, enforced per field and per object against the authenticated user, never assumed from the query shape — a client can always ask for a field it should not see, so the server must be the one to refuse. We also disable introspection and rich error detail in production where appropriate, rate-limit by query cost rather than request count, and treat every input type as untrusted, validated at the boundary like any other API.
Scalability
GraphQL scales on two axes: request load and organisational size. For load, the graph server is typically stateless and scales horizontally like any other stateless service, so the real bottlenecks are the data sources behind the resolvers. Batching, caching, and query-cost limits are what keep those data sources from being overwhelmed by expensive or high-fan-out queries — the graph is only as scalable as the discipline in its resolvers.
For organisational scale, federation is the answer to the monolithic-schema problem. As teams and surface area grow, a single schema owned by everyone becomes a coordination bottleneck; splitting it into subgraphs, each owned and deployed independently and composed by a gateway, lets teams move at their own pace while clients still see one graph. We adopt it when the number of teams genuinely warrants that seam, and we keep the schema itself governed — naming, deprecation, and versioning conventions — so the graph stays coherent as it grows rather than sprawling into an inconsistent surface.
GraphQL integrations & ecosystem
The technologies we most often pair with it — each links to how we work with it.
How we work
We start with the schema and the clients, not the database. Before any resolver is written we agree the types, the queries each client actually needs, and where the boundary sits between the graph and your existing services — because the schema is a contract, and a contract designed around storage rather than consumption is one you will fight for years. From there we build in thin vertical slices: a real query, resolved from real data, with batching and authorisation in place from the first field, running in production rather than a demo that quietly N+1s under load.
The engineers who design the graph are the ones who run it, which is why we build the unglamorous parts — DataLoader batching, depth and cost limits, persisted queries, a sane caching story — from the outset rather than after the first incident. That is what operating what we build means here. And because GraphQL is so often adopted by reflex, our first job is sometimes to talk you out of it: if your system is simple CRUD behind one client, or a public cacheable read API, we will tell you REST is the better choice before you spend money on a graph you do not need.
The service behind it
Delivered throughCustom Software DevelopmentWhat we build with GraphQL
The disciplines this technology most often shows up in — from a first build to taking over and stabilising an existing one.
How we deliver
- 01
Discover
We map the system, the constraints and the business it serves — including the parts nobody documented.
Architecture brief
- 02
Architect
Decisions get made, written down and defended before a line of production code exists.
Decision records
- 03
Build
Short cycles against working software. You see progress in the product, not in a status deck.
Shipping increments
- 04
Operate
Monitoring, incident response and iteration. The system is alive, so the engagement is too.
Runbooks & SLOs
Industries we use GraphQL in
Domain knowledge changes what gets built. A few of the sectors we know before the first meeting.
Also in Frontend
Why teams choose us for GraphQL
Senior engineers only
GraphQL is easy to stand up and easy to get wrong — the N+1 traps, the caching pitfalls, the query-cost exposure all bite teams learning on the job. The people designing your schema have run graphs in production and have seen how these decisions age. No juniors practising on your project.
We operate what we build
We run the graphs we ship, so we build batching, depth-limiting, and caching in from the start rather than after the first outage. We optimise for the eighteen-month maintenance reality, not the demo query that looks fast on an empty database.
Honest about when GraphQL is wrong
GraphQL is over-adopted, and we will say so. If your system is simple CRUD behind one client, or a public cacheable API, we will recommend REST and explain why — before you commit to complexity you will pay for indefinitely.
Typical timeline
- 01
Schema and boundary design
One to two weeks agreeing the schema, the queries each client needs, and where the graph sits over your existing services — the contract settled before resolver volume grows.
- 02
First vertical slice
Two to three weeks delivering one real query and mutation end to end in production, with DataLoader batching and authorisation in place, proving the design against real data and load.
- 03
Iterative build
Feature-by-feature delivery of the rest of the graph in short cycles, each slice shippable, with query-cost protection, caching, and generated client types kept current as we go.
- 04
Hardening and handover
Load testing against N+1 and expensive-query scenarios, depth and cost limits verified, persisted queries in place, and documentation so your team can own and extend the graph.
How pricing works
- Fixed-scope builds for a well-defined graph — a schema over known services, or a back-end-for-frontend for a specific set of clients — quoted once the schema and data sources are understood.
- Monthly senior engagement for evolving product work, where the schema grows with the product and you want continuity and governance rather than a one-off deliverable.
- Focused audits and rescues for existing GraphQL APIs — N+1 problems, missing query protection, caching, or a federation migration — priced by the assessment.
Hire GraphQL engineers
Need GraphQL capacity on your own team? We embed named senior engineers into your existing team — reporting to your leads, working in your rituals — so you add capacity without a hiring cycle.
Hire GraphQL engineersCommon questions
Do we actually need GraphQL, or would REST be simpler?
Often REST is simpler and we will say so. GraphQL earns its complexity when you have several clients needing different shapes of the same data, or screens that aggregate many services. For simple CRUD behind a single known client, or a public cacheable read API, REST is easier to build, easier to cache, and easier to run. We recommend GraphQL for a reason, not by default.
What is the N+1 problem and how do you avoid it?
A GraphQL query over a list resolves each item’s nested fields separately, so a naive server fires one database query per item — a hundred items, a hundred queries. We avoid it with DataLoader-style batching and per-request caching on every resolver that touches a data source, collapsing those into a few batched calls. It is designed in from the first resolver, because retrofitting it across a mature graph is genuinely painful.
How does caching work with GraphQL?
Not as easily as with REST, and we are honest about that. Because everything is a POST to one endpoint, you lose REST’s free URL-based HTTP and CDN caching. We recover what we can with a normalised client-side cache like Apollo or urql, and with persisted queries that make known operations cacheable. If your workload is mostly high-read and cache-friendly, that is a strong signal REST would actually be faster.
Is a GraphQL API harder to secure than REST?
It needs different, deliberate defences. A single endpoint where clients write their own queries is exposed to deeply nested or expensive queries that can exhaust the server, so we add depth limiting, query-cost analysis, timeouts, and persisted-query allow-lists. Authorisation is enforced per field and per object in the resolvers against the authenticated user — never assumed from the query — because a client can always ask for something it should not receive.
What is GraphQL federation and do we need it?
Federation composes several independently-owned subgraphs — say users, billing, and catalogue, each run by a different team — into one supergraph behind a gateway, so clients see a single graph while ownership stays distributed. It is worth its machinery when you have multiple teams with real service boundaries. For a single team that could own one schema, it is pure overhead, and we will steer you away from it.
Building on GraphQL?
A technical conversation with the engineers who would do the work. If we are not the right fit, we will say so on the call.