PROPELOO

GRAPHQL API ENGINEERING

Build a GraphQL API that performs under real query load.

PROPELOO engineers production GraphQL APIs — from schema design and resolver architecture through DataLoader (N+1 prevention), subscriptions, persisted queries, complexity limiting and the monitoring that catches slow resolvers before users do. GraphQL is not easier than REST. It trades one set of problems for another. We solve the GraphQL-specific ones.

A GraphQL API without DataLoader will perform worse than the REST API it replaced.

GraphQL's flexibility is its most dangerous feature in the wrong hands. A query that asks for 100 users with their posts with their comments makes 1 + 100 + N database queries if resolvers are not batched with DataLoader. The N+1 problem is GraphQL's most common performance failure and it is entirely invisible until the query runs on production data. Additionally: a deeply nested query can traverse millions of nodes unless query complexity limiting is implemented; schema introspection exposes your entire API surface unless restricted; field-level performance monitoring requires specific tooling not included in basic GraphQL setup. PROPELOO builds GraphQL APIs where DataLoader is in every list resolver, complexity limits are calibrated to real queries, persisted queries are enabled for production clients and resolver performance is monitored at the field level.

The GraphQL engineering stack.

System Layers

  • Schema Layer: Type definitions, resolvers, schema stitching, codegen, schema validation
  • Performance Layer: DataLoader batching, N+1 elimination, caching, query complexity limits, depth limits
  • Real-time Layer: Subscriptions via WebSocket, server-sent events, live queries
  • Security Layer: Authentication, field-level authorisation, introspection control, rate limiting
  • Federation Layer: Apollo Federation for multi-service schema composition, subgraphs

Core Technical Capabilities

  • Schema-first Design

    Schema written before resolvers — type definitions reviewed as an API contract. Code-first with Pothos for TypeScript type safety. GraphQL Code Generator for typed client hooks. Relay cursor pagination spec for consistent pagination.

  • DataLoader & N+1 Prevention

    DataLoader in every resolver that fetches related data — batches all ID lookups within a request into single database queries. Request-scoped DataLoader instances. Cache keyed by ID. Eliminates the most common GraphQL performance failure.

  • GraphQL Subscriptions

    Real-time data via WebSocket subscriptions (graphql-ws protocol), filtered subscriptions per user/entity, subscription authorisation, connection lifecycle management and Redis pub/sub for multi-instance subscription fan-out.

  • Query Complexity & Depth Limits

    Field-level cost assignment, maximum query complexity threshold, maximum query depth, query timeout, field execution timeout and query allowlist for production clients.

  • Apollo Federation

    Apollo Federation v2 for composing multiple subgraph services into a unified schema. Router configuration, subgraph SDL, @key directives, @external fields and reference resolvers.

  • Field-level Monitoring

    Apollo Studio or custom resolver telemetry — p95 latency per field, error rate per field, operation usage statistics and slow resolver alerting.

How we think about GraphQL.

GraphQL solves the over-fetching and under-fetching problems of REST. It introduces the N+1 query problem, query complexity attacks and schema governance complexity. Know what you are trading.

  • DataLoader is not optional

    Every GraphQL API that returns lists of objects with related data will produce N+1 queries without DataLoader. This is not a performance optimisation to add when it becomes a problem — it is a correctness requirement from day one. A resolver that makes a database query per list item will produce 100 queries for a list of 100 items. DataLoader collapses all 100 into one.

    Axiom:

  • Schema is a public contract

    Once clients are using a GraphQL schema in production, every field you remove is a breaking change. Schema deprecation (@deprecated directive), versioning strategy and client usage monitoring via field-level analytics are required before removing any field. GraphQL's schema flexibility makes evolution easy in theory and dangerous in practice without governance.

    Axiom:

  • Subscriptions need different infrastructure

    HTTP-based GraphQL queries can use standard stateless web infrastructure. WebSocket subscriptions maintain persistent connections — a 10,000 concurrent user application needs infrastructure sized for 10,000 persistent WebSocket connections, not just the average HTTP request rate. Redis pub/sub is required for subscription fan-out across multiple server instances.

    Axiom:

  • Introspection must be disabled in production

    GraphQL introspection returns the complete schema — all types, all fields, all directives. This is essential in development and a security risk in production where it exposes your entire API surface to attackers. Disable introspection in production, enable it for specific client IPs or authenticated developer tokens.

    Axiom:

GraphQL architecture decisions.

  • Code-first vs schema-first?

    Impact: Pothos (code-first) for TypeScript projects — type safety from resolver to client, no schema drift. SDL schema-first for teams that want to design the schema as a standalone contract.

    • Schema-first (SDL) — schema reviewed as documentation, resolvers implement it
    • Code-first (Pothos/TypeGraphQL) — types generated from code, TypeScript integration
    • Hybrid — SDL for external types, code-first for internal resolvers
  • Apollo Server vs GraphQL Yoga vs other?

    Impact: Apollo Server for Federation requirements. GraphQL Yoga for modern Node.js/Edge environments. Hasura for rapid prototyping on PostgreSQL with minimal custom business logic.

    • Apollo Server 4 — most features, Apollo Federation, Apollo Studio
    • GraphQL Yoga — modern, WinterCG compatible, framework-agnostic
    • Mercurius (Fastify) — Fastify-native, good performance
    • Hasura — auto-generated from PostgreSQL, minimal custom logic
  • Pagination approach?

    Impact: Relay cursor spec for consistency and client library compatibility. Offset for simple admin interfaces where data stability is not critical.

    • Relay cursor spec — standardised, supports bi-directional, most complex
    • Offset pagination — simple, breaks on concurrent inserts
    • Keyset pagination — stable, simple implementation
    • None — only for small bounded lists
  • Federation vs monolithic schema?

    Impact: Monolithic schema for single-team products. Federation for multiple teams owning different domains where independent schema evolution and deployment is required.

    • Federation — multiple subgraphs composed at router, team autonomy
    • Monolithic schema — single service, simpler, right for most teams
    • Schema stitching — older pattern, largely replaced by Federation
  • Client-side: Apollo Client vs urql vs React Query?

    Impact: urql for new projects — lighter than Apollo, composable. Apollo Client where normalised cache (optimistic updates, cache manipulation) is required. React Query for simpler needs.

    • Apollo Client — feature-rich, normalised cache, large bundle
    • urql — lighter, composable, good default
    • React Query + graphql-request — simplest, no normalised cache
    • Relay — most opinionated, best performance, highest learning curve
  • Real-time via subscriptions or polling?

    Impact: Subscriptions for true real-time requirements (chat, live dashboards). SSE for unidirectional real-time (notifications, feed updates). Polling for data that tolerates seconds of latency.

    • GraphQL subscriptions (WebSocket) — true real-time, persistent connection overhead
    • HTTP polling — simple, adds latency equal to poll interval
    • Server-sent events (Yoga) — unidirectional real-time, simpler than WS
    • Live queries (experimental) — automatically re-execute on data change

What PROPELOO builds.

  • Multi-client GraphQL API

    Single GraphQL API serving web, mobile and partner clients — each client queries exactly the data it needs, DataLoader prevents N+1, field-level monitoring.

  • Real-time GraphQL

    Subscriptions with Redis pub/sub fan-out for chat, live notifications and collaborative features — handles 10K+ concurrent WebSocket connections.

  • Federated GraphQL Platform

    Apollo Federation v2 with multiple subgraphs — user service, product service, order service — unified schema via Apollo Router.

  • GraphQL Performance Audit

    N+1 query identification, DataLoader implementation, query complexity calibration and resolver performance monitoring for existing GraphQL APIs.

  • DeFi/Web3 GraphQL API

    GraphQL API over blockchain data from The Graph subgraphs — resolvers that blend indexed on-chain data with off-chain metadata.

  • Admin Dashboard API

    Internal GraphQL API for admin tools — complex filtering, multi-entity queries, field-level authorisation by admin role.

The GraphQL stack.

  • Server

    Stack: Apollo Server 4, GraphQL Yoga, Pothos (code-first), Mercurius (Fastify), Nexus

  • Performance

    Stack: DataLoader, graphql-query-complexity, graphql-depth-limit, graphql-rate-limit

  • Real-time

    Stack: graphql-ws, Redis pub/sub, Server-sent events

  • Federation

    Stack: Apollo Router, Apollo Federation v2, @apollo/subgraph

  • Client

    Stack: Apollo Client, urql, React Query + graphql-request, Relay

  • Tooling

    Stack: GraphQL Code Generator, Apollo Studio, GraphQL Inspector, Altair

GraphQL security requires specific mitigations.

  • Disable introspection in production

    Introspection exposes the full schema. Disable in production, allow only for authenticated developer tokens or specific IPs.

  • Query complexity limits

    A deeply nested query (users → posts → comments → likes → users → ...) can create an exponential resolver chain. Field-cost-based complexity limits prevent this.

  • Field-level authorisation

    Not every authenticated user should access every field. Sensitive fields (salary, PII, internal metadata) require field-level authorisation checks in resolvers, not just route-level auth.

  • Persisted queries

    Persisted queries (APQ or allowlist) restrict production to only pre-registered queries — eliminates ad-hoc query injection attacks and reduces request payload size.

  • Batching attack prevention

    GraphQL batching allows sending multiple operations in one HTTP request. Rate limiting must apply to individual operations within a batch, not just the HTTP request.

  • Resolver timeout

    Individual resolver execution timeout prevents a slow database query from holding the entire request indefinitely. Configure per-field timeouts in Apollo Server resolver context.

From schema to production GraphQL.

  1. 01. Schema Design

    Type definitions, query/mutation/subscription design, pagination approach, federation planning.

  2. 02. Resolver Architecture

    DataLoader setup, context design, authorisation middleware, error handling.

  3. 03. Core Resolvers

    Query and mutation resolvers with DataLoader batching and field authorisation.

  4. 04. Performance Configuration

    Query complexity limits, depth limits, persisted queries, caching configuration.

  5. 05. Subscriptions

    WebSocket setup, Redis pub/sub, subscription filters and connection lifecycle.

  6. 06. Monitoring

    Field-level performance tracking, slow resolver alerts, error rate per operation.

  7. 07. Client Integration

    GraphQL Code Generator setup, typed client hooks, subscription client.

Frequently Asked Questions

REST vs GraphQL — when does GraphQL win?

GraphQL wins when: multiple clients (web, mobile, partner) need different data shapes from the same API, over-fetching is causing performance problems, under-fetching requires multiple round trips, or clients need ad-hoc data exploration (admin tools, analytics). REST wins for: simple APIs with well-defined endpoints, public APIs where universal client support matters, file upload-heavy APIs, or teams without GraphQL expertise.

What is the N+1 problem?

When a GraphQL resolver returns a list of 100 users, and each user has a "posts" field that fetches from the database, the result is 1 query for users + 100 queries for posts = 101 queries. DataLoader fixes this by collecting all the post IDs requested within a single request tick and fetching them in a single query. Without DataLoader, GraphQL APIs perform worse than REST on list queries.

How do subscriptions work in production?

GraphQL subscriptions use WebSocket protocol (graphql-ws). Each connected client maintains a persistent WebSocket connection. When data changes (new message, updated order), the server pushes the update to subscribed clients. At scale, multiple server instances each handle a subset of connections. Redis pub/sub fan-out ensures all server instances can publish to all subscribed clients.

What is Apollo Federation?

Apollo Federation allows composing multiple independent GraphQL services (subgraphs) into a unified schema via an Apollo Router gateway. Teams own their subgraphs independently — the user service owns the User type, the product service owns the Product type. The Router stitches them together at runtime. A query that spans both types is split and executed against the appropriate subgraphs.