I’m sharing what I learned building a production GraphQL API: how to design query and mutation architecture that scales without losing consistency or clarity. This article walks through a code-first HotChocolate implementation using BatchDataLoader, CQRS, and the transactional outbox pattern, with real examples from an EMR system serving three portals via a single schema.
Key takeaways:
— How to structure GraphQL queries and mutations cleanly in a .NET code-first approach
— Using BatchDataLoader to prevent N+1 problems while keeping resolvers maintainable
— Applying CQRS to separate read and write concerns without complicating the API layer
— Pairing writes with a transactional outbox pattern for reliable, production-grade event delivery
Read the full article here: https://erwinwilsonceniza.qzz.io/blogs/graphql-query-mutation-architecture-a-production-deep-dive
Top comments (1)
Nice combination — outbox + CQRS is the pairing that quietly saves you at 2am. Two things I'd add, since you framed it as production-grade:
The outbox gives you at-least-once delivery, so the consumer side has to be idempotent or you'll double-apply on redelivery. Worth stating right next to the outbox, because people build the reliable producer and then forget the dedup on the other end.
For GraphQL specifically, BatchDataLoader kills N+1 within a request, but the other production footgun is query cost — one deeply-nested query can fan out into thousands of loads. Depth + complexity limits (and persisted queries, if the three portals are first-party clients) are what stop a client from writing an accidental DoS. Per-operation cost analysis on a shared schema also shows you which portal is driving load.
Curious how you're scoping the DataLoader lifecycle, per-request vs per-operation, with three portals on one schema — that's usually where the maintainability gets tested.