Architecture

Collapsing a fragmented REST surface into one GraphQL gateway. Here's what broke first.

The pattern for consolidating an enterprise platform onto HotChocolate behind Azure APIM — including the directive design that finally made field-level authorization survive a security audit.

150+ → 1
REST endpoints consolidated to one graph
HotChocolate
.NET gateway behind Azure APIM
Field-level
deny-by-default authorization in the schema

The problem

Platforms accrete endpoints. Over years, this one had grown 150+ REST endpoints across many services — overlapping, inconsistently authorized, and impossible for a client team to navigate without tribal knowledge. Each new screen meant orchestrating several calls, stitching their responses together, and reconciling several different authorization models on the client.

The deeper cost wasn't any single endpoint — it was that the surface had no single shape. Onboarding a client integration meant rediscovering which calls existed, which were authoritative, and which auth scheme each expected. That's the kind of friction that compounds invisibly until it dominates delivery time.

What broke first

The first thing that breaks when you put GraphQL in front of existing services is the N+1 problem. A naïve resolver issues one backend call per field per row, so a single query that looks clean to a client fans out into a storm of REST calls against the services behind it. The fix is batching and caching at the resolver layer — HotChocolate's DataLoader coalesces the per-row lookups into batched requests per tick — plus deliberate decisions about what to resolve eagerly versus on demand.

A gateway that just forwards every field as a backend call doesn't simplify the platform — it moves the mess one layer up.

The directive design that survived the audit

Field-level authorization is where most GraphQL consolidations quietly fail an audit: the schema exposes a field and trusts something else, somewhere, to protect it. The design that held treats authorization as a property of the schema itself:

  • Authorization as a schema directive. HotChocolate's @authorize(policy: …) attaches a policy to a type or an individual field and is evaluated inside the execution pipeline — so the rule travels with the field rather than living in a resolver a reviewer has to go find.
  • Deny-by-default. Sensitive fields require an explicit policy; the safe posture is that an unprotected field is a build-time problem, not a runtime surprise. Enforcing "every PHI field carries a policy" as a check keeps coverage from eroding as the schema grows.
  • Auditable mapping. Because policies are declared on the schema, the policy-to-field mapping is readable straight from the SDL — a reviewer can verify coverage without running the app or tracing resolver code.
type Patient {
  id: ID!
  name: String! @authorize(policy: "patient:read")
  ssn: String   @authorize(policy: "phi:read")
}

The shift that mattered was from endpoint-level to field-level. With REST, authorization lived at the route and a single over-permissive endpoint could leak a sensitive field bundled into a larger payload. Pushing the policy down to the field — declared in the schema, enforced in the pipeline — closes that class of gap and makes the protection auditable rather than assumed.

Sitting it behind Azure APIM

APIM and the schema do different jobs, and keeping that boundary clean is the point. APIM handles the edge concerns — TLS, JWT validation, rate limiting, throttling, request shaping — so the gateway can assume it only ever sees authenticated, well-formed traffic. What deliberately does not go into APIM is business authorization: field-level access control belongs in the schema where it's expressive and auditable, not encoded into gateway policy rules where it would drift out of sync with the graph. Edge gateway guards the door; the schema guards the data.

Outcome

The platform went from a sprawl of overlapping REST endpoints to a single, navigable graph: clients ask for exactly the shape they need in one request, and authorization is uniform and visible across the whole surface. The consolidation paid off twice — once in client integration speed, and once in security posture, because authorization became something you can read off the schema instead of reconstruct from scattered route handlers.

Where GraphQL isn't the answer: high-volume binary or streaming endpoints, simple webhooks, and file transfer all stayed REST. The gateway's job was to unify the application data surface — not to become the single pipe for everything the platform does.