Migrating REST to GraphQL: A Step-by-Step Strategy Without a Big Bang
AI generated
{ }
type
GraphQL · REST API · Migration · BFF
Migrating REST to GraphQL
the step-by-step strategy without a big bang

Replacing an existing REST API with GraphQL in one shot is the surest way to trigger production outages. With a BFF layer, the strangler fig pattern, and a clear rollout plan, you can migrate REST to GraphQL while both protocols coexist safely, side by side, for months.

18 min read BFF · Strangler Fig · Schema-first · Resolver Rollout GraphQL 16 · REST · Legacy Migration

1. Why a big bang fails at GraphQL migration

The desire to replace a grown REST API entirely with GraphQL is understandable, but the direct path there almost always fails in practice. Anyone migrating REST to GraphQL by rebuilding every endpoint at once and cutting all clients over on a single date risks a weeks-long freeze on new features, while regressions creep into production-critical areas at the same time. The big bang forces every piece of business logic to be tested a second time before the old path can even be turned off.

The alternative is an incremental strategy in which REST and GraphQL exist side by side for months while traffic shifts from REST to GraphQL piece by piece. Anyone treating migrating REST to GraphQL as a continuous process rather than a project with a fixed end date can pause anytime capacity is tight and resume anytime it frees up again. This flexibility is the real value of a gradual migration, not merely the reduced risk surface.

2. The BFF layer as a safe starting point

The most practical first step for migrating REST to GraphQL is a backend for frontend layer that speaks GraphQL on the outside and calls the existing REST endpoints internally. This BFF layer changes nothing in the backend services at first, it merely adds a translation layer. Frontend teams can start writing GraphQL queries immediately, even though the underlying data still comes from the same REST services that have been running in production for years.

This approach drastically reduces risk because the business logic in the backend services stays unchanged. The BFF handles only aggregation, type conversion and field selection, classic GraphQL strengths that REST does not offer. Only in a second step, once the BFF layer runs stably, are individual REST calls in the resolver replaced by direct database access or native GraphQL services, without clients noticing anything.


# BFF schema: mirrors what the REST API already exposes,
# just reshaped into a typed, queryable graph
type Order {
  id: ID!
  status: OrderStatus!
  items: [OrderItem!]!
  customer: Customer!
  total: Money!
}

type Query {
  order(id: ID!): Order
  orders(customerId: ID!, limit: Int = 20): [Order!]!
}

// BFF resolver: thin adapter calling the existing REST endpoint,
// no business logic duplicated here
const resolvers = {
  Query: {
    order: async (_parent, { id }, { restClient }) => {
      // Still hits the legacy REST endpoint under the hood
      const response = await restClient.get(`/api/orders/${id}`);
      return mapRestOrderToGraphQL(response.data);
    },
  },
};

function mapRestOrderToGraphQL(restOrder: RestOrder): Order {
  return {
    id: restOrder.order_id,
    status: restOrder.status.toUpperCase(),
    items: restOrder.line_items.map(mapLineItem),
    customer: { id: restOrder.customer_id, name: restOrder.customer_name },
    total: { amount: restOrder.grand_total, currency: restOrder.currency_code },
  };
}

3. Strangler fig pattern: retiring REST endpoints step by step

The strangler fig pattern, named after the fig tree that slowly envelops and replaces a host tree, describes exactly the migration logic behind migrating REST to GraphQL: new functionality is built directly in GraphQL, existing functionality is pulled into the GraphQL layer endpoint by endpoint, field by field, while the old REST code keeps running until no resolver depends on it anymore. The old tree only dies once it is fully enveloped.

The order matters: you do not start with the most complex endpoint, but with a well-bounded, low-traffic area, to establish process and tooling with low risk. Only once this first slice runs successfully in production does the next, larger area follow. This ordering turns every migration phase into an independent, rollback-able release, instead of a monolithic overhaul that has to work as a whole or fail entirely.

4. Schema-first: designing the GraphQL schema before the code

During a migration, the temptation is strong to derive the GraphQL schema one to one from existing REST response structures. But that carries historical REST baggage, inconsistent field names, redundant IDs, nested wrapper objects, unchanged into the new schema. Anyone approaching migrating REST to GraphQL the right way designs the target schema independently of the current REST structure, oriented around what clients actually need.

In practice this schema-first approach means: first define the ideal query shape together with the frontend teams, then build the resolver implementation backwards, assembling that shape from the existing REST data. All the translation work lands in the resolver, not in the schema. That creates more mapping code in the short term, but prevents technical debt from the REST era from being permanently cemented into the new GraphQL contract.


# Target schema designed independently from legacy REST shape -
# clean naming, no redundant wrapper objects, proper types
type Customer {
  id: ID!
  email: String!
  fullName: String!
  addresses: [Address!]!
  loyaltyTier: LoyaltyTier!
}

# Legacy REST response for comparison (what the resolver translates FROM):
# {
#   "cust_id": "8842",
#   "email_addr": "jane@example.com",
#   "first_name": "Jane",
#   "last_name": "Doe",
#   "addr_list": { "items": [...], "count": 2 },
#   "tier_code": "GOLD_02"
# }

5. Resolvers as thin adapters over existing REST endpoints

During the transition phase, GraphQL resolvers in this migration are deliberately kept thin: they call existing REST endpoints, transform the response into the target shape and return it. No new business logic is created in the resolver itself, it stays entirely inside the existing backend service. This discipline prevents business rules from ending up duplicated in two places during migration, which would otherwise cause inconsistent behavior between REST and GraphQL later on.

A central pitfall of this approach is N plus one: if a GraphQL resolver for a list of orders calls a REST endpoint again for every single order, hundreds of HTTP requests result, where the old REST API delivered a single response with embedded data. A DataLoader that batches and deduplicates REST calls within one request cycle is therefore mandatory from the first production resolver onward, not an optional optimization for later.


// DataLoader wraps the legacy REST endpoint to prevent N+1
// during the transition period, before native batching exists
import DataLoader from "dataloader";

const customerLoader = new DataLoader<string, Customer>(async (ids) => {
  // Legacy REST endpoint supports batch fetch via query param
  const response = await restClient.get("/api/customers", {
    params: { ids: ids.join(",") },
  });
  const byId = new Map(response.data.map((c: RestCustomer) => [c.cust_id, c]));
  return ids.map((id) => mapRestCustomerToGraphQL(byId.get(id)));
});

6. Parallel coexistence: REST and GraphQL in the same system

For the entire migration period, often six to eighteen months, REST and GraphQL must serve the same data consistently. Anyone migrating REST to GraphQL without data integrity problems has to make sure write operations through both paths trigger the same validation and the same side effects, such as event publishing or cache invalidation. A mutation resolver that changes order data directly in the database while the parallel REST endpoint still fires an event otherwise produces silent inconsistencies.

The most pragmatic path is to route mutations through the existing REST write path initially too, just like queries, and only later implement independent write logic in the GraphQL layer. That delays fully retiring REST internally, but guarantees that only one single place exists throughout the entire coexistence phase where business rules for write operations are maintained.

7. Migrating clients gradually without breaking changes

Migrating the clients themselves is the part of the migration that requires the most coordination with the rest of the team. Instead of an app-wide switch that forces a single deploy day, migrating REST to GraphQL should happen at the feature level: a new feature is built directly with GraphQL, an existing screen is migrated during its next planned rework anyway, critical legacy areas deliberately stay on REST until their migration has a clear business justification.

Feature flags are the most important technical tool here: a flag per migrated screen lets you enable GraphQL for a subset of users, compare error rates and latency, and fall back to REST instantly if something goes wrong, without rolling out a new client release. This fallback option is the single most important safety mechanism of the entire strategy throughout the transition period.

8. Safely retiring REST endpoints: traffic-driven decisions

The final and often underestimated step is actually retiring old REST endpoints. Without solid traffic data, teams tend to keep endpoints alive for years out of sheer caution, even when no client accesses them anymore. Anyone wanting to cleanly finish migrating REST to GraphQL instruments every REST endpoint with access metrics before migration even begins, and defines a clear threshold, for example zero requests over thirty days, at which point an endpoint counts as retirable.

A two-stage process has proven effective: first, the endpoint gets a Deprecation header visible in response headers and logs, followed by a transition period of typically four to eight weeks during which remaining callers are identified and actively contacted. Only afterwards is the endpoint removed, never without a preceding observation phase, no matter how confident anyone feels that nobody accesses it anymore.

9. Migration strategies head to head

Not every migration strategy suits every team. The table below compares common approaches for migrating REST to GraphQL.

Strategy Risk Time investment Suited for
Big bang rewrite Very high Short but riskily concentrated Small, low-criticality systems
BFF with REST passthrough Low Ready to start immediately Production-critical legacy systems
Strangler fig, slice by slice Low to medium Long but plannable Large, complex REST landscapes
Parallel rebuild without BFF High Double maintenance during transition Rarely advisable

The combination of a BFF entry point followed by a strangler fig continuation delivers the best risk-to-speed ratio in practice. It allows working with GraphQL queries immediately, without waiting for a complete backend migration, while establishing a clear, iterative path toward fully retiring REST.

Mironsoft

GraphQL migration, BFF architecture and Magento integration

Migrating your REST API to GraphQL step by step?

We design a low-risk migration plan with you, build the BFF layer as a safe entry point, and support the strangler fig rollout all the way to fully retiring your legacy endpoints.

Migration plan

Slice prioritization, traffic analysis and rollout order for your REST landscape

BFF implementation

GraphQL schema design and thin resolvers over your existing REST endpoints

Client migration

Feature-flag-driven migration of your frontend teams without breaking changes

10. Summary

Anyone migrating REST to GraphQL should rule out a big bang from the very start. A BFF layer that speaks GraphQL on the outside while calling existing REST endpoints internally enables an immediate start without backend risk. The strangler fig pattern pulls functionality into the new layer slice by slice, while the old code keeps running until no resolver depends on it anymore.

Three disciplines are decisive for success: a schema-first target schema instead of a one-to-one takeover of the REST structure, consistent DataLoader batching against N plus one in transition resolvers, and traffic metrics as the only reliable basis for finally retiring old endpoints. Anyone who follows these three points can be migrating REST to GraphQL without ever having to plan a risky cutover day.

Migrating REST to GraphQL — The Essentials at a Glance

BFF as entry point

GraphQL layer in front of existing REST endpoints, no backend rebuild needed, ready to start immediately.

Strangler fig

Migrate slice by slice, every phase independently releasable and rollback-able.

Schema-first

Design the target schema independently of REST baggage, mapping work belongs in the resolver.

Traffic-driven retirement

Collect metrics first, then deprecate, only remove endpoints afterward.

11. FAQ: Migrating REST to GraphQL

1Why does a big bang fail?
Switching all endpoints and clients at once concentrates risk into a single moment and makes rollbacks extremely expensive.
2What is a BFF layer?
Speaks GraphQL outside, calls existing REST endpoints inside. No backend rebuild required, immediate start possible.
3What does strangler fig mean?
Migrate slice by slice, old code stays until the last dependency is gone, then it dies.
4Derive schema from REST responses?
No, that carries baggage over unchanged. Design schema-first, put mapping work in the resolver.
5Prevent N plus one in transition resolvers?
DataLoader batches REST calls per request cycle. Without it, lists easily produce hundreds of individual requests.
6Mutations during the transition phase?
Route through the existing REST write path first, so business rules are maintained in one place only.
7Migrate clients without breaking changes?
Feature-flag-driven per screen, allows instant fallback to REST if problems appear.
8When is an endpoint safely retirable?
After access metrics, a deprecation header, and a multi-week transition period without meaningful traffic.
9How long does migration typically take?
For medium-sized systems, realistically six to eighteen months, depending on scope and available capacity.
10Must REST disappear completely?
No, some endpoints, such as webhooks or third-party integrations, sensibly remain REST-based permanently.