REST to GraphQL Migration Workflow
A reusable, phase-by-phase playbook for migrating a REST API to GraphQL with zero downtime: gradual rollout, backwards compatibility, codegen, schema versioning, and a cutover checklist.
REST to GraphQL Migration Workflow#
A Reusable Pattern, Not a Story
This page is a technical reference, distilled from a real production migration, that you can apply directly to your own REST-to-GraphQL migration. It's organized as a repeatable pattern and checklist rather than a narrative.
Reference Case#
Real-World Result: REST → GraphQL at Scale
Developers Migrated
Entire external developer ecosystem moved to GraphQL
Downtime
Achieved through a dual-stack, gradual rollout
API Performance
Measured improvement after migration
Support Tickets
Reduction in API-related support volume
These figures come from a Bazaarvoice REST-to-GraphQL migration. See Case Studies and the longer-form write-up, REST to GraphQL: The Migration That Saved Our API, for the full story. The workflow below generalizes that migration into steps you can apply regardless of stack.
When to Use This Workflow#
GraphQL migration tends to pay off when you have:
- Multiple client types (web, iOS, Android) with different data-shape needs from the same backend
- Deeply nested or relational data that currently requires several chained REST calls per view
- A large, versioned endpoint surface that's expensive to document and maintain
- Frontend teams blocked on backend changes for every new UI data requirement
- A big enough developer base that documentation and onboarding cost is a real line item
When to Stick with REST#
Migrating isn't automatically the right call. Stay on REST (or scope the migration down) when:
- Your API is mostly simple CRUD with few relationships to traverse
- It's a public API you don't control the clients for — arbitrary GraphQL queries are harder to secure and cache than fixed REST routes
- You have heavy file upload/download workflows (GraphQL doesn't handle binary payloads natively)
- The team has no GraphQL experience and the timeline doesn't allow for a learning curve
- The existing REST API already works well and developer feedback is positive — don't migrate for trend's sake
The Gradual Rollout: Strangler Fig, Not Big Bang#
Replace REST endpoints incrementally while both stacks run in production side by side, rather than attempting a single cutover. This is the "Strangler Fig" pattern applied to APIs.
Foundation
Design the schema around your domain graph. Stand up a GraphQL layer whose resolvers call your existing services — no business logic moves yet. Ship developer tooling (GraphQL Playground/Explorer, auto-generated schema docs) from day one.
Parallel Operation
New features ship GraphQL-only. REST stays fully supported for existing integrations. Both stacks read from the same underlying services, so behavior stays consistent.
Strategic Cutover
Migrate existing REST consumers by priority (highest traffic and highest support burden first), not by convenience. Move one integration at a time and monitor before moving the next.
Deprecation & Sunset
Mark migrated REST endpoints deprecated, track remaining usage, and retire them only after usage drops to zero (or an agreed floor) — never on a fixed calendar date alone.
Backwards-Compatibility Strategy#
The core rule: REST and GraphQL read from the same service layer, so neither stack can drift out of sync while both are live.
// GraphQL resolvers delegate to the same services REST controllers already use —
// no duplicated business logic, no risk of the two APIs disagreeing.
const resolvers = {
Query: {
user: async (_, { id }) => userService.findById(id),
},
User: {
projects: async (user) => projectService.findByUser(user.id),
},
};
Practical rules that keep this safe:
- Never remove or repurpose a REST endpoint until its GraphQL replacement has shipped and its usage has been migrated
- Version by field, not by endpoint — GraphQL has no
/v2/equivalent; additive schema changes are the versioning mechanism - Put both stacks behind the same auth, rate limiting, and observability layer so switching costs nothing for consumers
- Publish a side-by-side migration guide for every endpoint you move, showing the old REST call and the equivalent GraphQL query
Type-Safe Codegen Approach#
Treat the schema as the single source of truth and generate code from it in both directions:
- Server-side: generate resolver/type signatures from the schema (e.g. GraphQL Code Generator) so resolver implementations can't drift from the contract
- Client-side: generate typed query/mutation hooks for each client (Apollo Client for web, Apollo iOS/equivalent for mobile) instead of hand-writing fetch calls
- CI gate: run codegen and schema-diff checks on every pull request; a PR that introduces a breaking schema change should fail CI, not get caught in production
- Contract tests: validate that persisted/allow-listed queries from each client still resolve correctly against the current schema before deploy
Schema Versioning Without Breaking Clients#
GraphQL has one schema for all consumers, so every change has to be evaluated for whether it's additive or breaking.
| Change | Safe? | Why |
|---|---|---|
| Add an optional field | ✅ Safe | Existing queries don't reference it, so nothing breaks |
| Add a new type or query | ✅ Safe | Purely additive to the graph |
Deprecate a field with @deprecated | ✅ Safe | Field still resolves; clients get a warning, not an error |
| Remove a field | ❌ Breaking | Any client still selecting it fails |
| Rename a field | ❌ Breaking | Equivalent to removal from the client's perspective |
| Change a field's type | ❌ Breaking | Existing selections may no longer typecheck |
Deprecate before you remove:
type User {
name: String!
email: String! @deprecated(reason: "Use contactInfo.email instead")
contactInfo: ContactInfo!
}
- Track deprecated-field usage in production analytics before scheduling removal
- Keep a schema changelog so consumers can see what changed and when
- Only remove a deprecated field once usage has dropped to zero (or an agreed-upon floor) for a full deprecation window
Guardrails: Performance & Reliability#
A single flexible endpoint introduces failure modes REST doesn't have — plan for them up front:
- N+1 queries — batch and cache per-request data fetching with a loader pattern (e.g. DataLoader) so resolving a list of parents doesn't issue one query per child
- Expensive/abusive queries — enforce query complexity or cost limits so a single query can't request unbounded nested data
- Caching — REST caches by URL; GraphQL needs a normalized cache (e.g. Apollo's
InMemoryCache) or a persisted-query allowlist in production - Monitoring — REST-era, per-URL dashboards don't work against one endpoint; adopt GraphQL-aware tooling (e.g. Apollo Studio or equivalent) that reports per-operation, not per-route
Migration Priority: What to Move First#
When deciding which REST endpoints to migrate next, rank by:
- Usage volume — highest-traffic endpoints first; they generate the most benefit per unit of migration effort
- Developer pain — endpoints with the most support tickets or "how do I get X" questions
- Maintenance burden — endpoints that change often or accumulate the most bugs
- Blast radius — prefer endpoints with fewer, well-known consumers before tackling ones with unknown/public callers
Cutover Checklist#
Pre-Migration Assessment
Migration Planning
Implementation
Cutover & Post-Migration
Decision Framework: REST vs. GraphQL#
Choosing Per-Endpoint, Not All-or-Nothing
Leave this endpoint as-is for now
✅ Pros:
- •Simple, well-understood caching
- •No client-side query risk
- •Lower migration cost
❌ Cons:
- •Multiple round trips for related data
- •Over/under-fetching on mobile
- •Endpoint sprawl over time
Move this endpoint into the schema
✅ Pros:
- •Single request for nested data
- •Self-documenting via introspection
- •Frontend queries evolve independently
❌ Cons:
- •Requires N+1/complexity guardrails
- •Needs normalized caching strategy
- •Team learning curve
Related Reading#
- Case Studies — the Bazaarvoice REST-to-GraphQL migration and its results
- REST to GraphQL: The Migration That Saved Our API — the full migration story this workflow is drawn from
- API Reference — this site's own API documentation
- Rate Limiting and Authentication — guardrails that apply to both REST and GraphQL endpoints