· 4 min read
How to Plan a Move From REST to GraphQL
Heshan Fernando
Co-founder & COO
Someone proposes moving the API to GraphQL. The first question is how much work it is, and the first instinct is to count endpoints — forty endpoints, forty resolvers, a few weeks.
The endpoint count is the part that maps mechanically. It is also the part that delivers the least value, and estimating from it produces a schema that works and nobody enjoys using.
What maps automatically
A REST endpoint list gives you the mechanical translation:
Collection GETs become list queries. GET /users becomes users: [User!]!.
Single-resource GETs become single queries with an id argument. GET /users/{id} becomes user(id: ID!): User.
POST, PATCH and DELETE become mutations, with input types derived from the request bodies.
Response schemas become object types.
That is a real amount of work saved, and it is roughly half the job.
What does not map, and why it matters
The value of GraphQL is the graph — being able to ask for a user, their orders, and each order’s items in one request, shaped exactly as the client needs.
A REST path list contains no information about those relationships. GET /users/{id} and GET /users/{id}/orders tell you an association exists; they say nothing about whether Order should expose customer, whether that traversal should be paginated, or which direction clients will actually query.
So the generated schema gives you types and operations with no edges between them — which is REST with a different transport. Designing the edges is the work, and it is a modelling exercise rather than a translation.
| Aspect | Maps from REST? |
|---|---|
| Types and fields | Yes |
| Queries and mutations | Yes |
| Input types | Mostly |
| Relationships between types | No |
| Pagination strategy | No |
| Authorisation per field | No |
The N+1 problem is the trap
A naive implementation resolves each field by calling the corresponding REST endpoint. Ask for fifty users and their orders, and the server makes one call for the users and fifty more for the orders.
That is the N+1 problem, and it is the standard way a first GraphQL implementation ends up slower than the REST API it replaced.
The fix is batching — collecting the ids requested within a single tick and fetching them together, which is what DataLoader and equivalents exist for. It is not something the schema expresses; it belongs in the resolver layer, and it needs to be planned in rather than added after a performance complaint.
Endpoints that should disappear
A mature REST API accumulates endpoints that exist to work around REST’s shape:
- Bulk fetch endpoints that take a list of ids
- Denormalised views combining several resources for one screen
- Compound update endpoints that modify several things at once
In a well-designed graph, the first two are unnecessary — clients ask for what they need. The third often becomes a single mutation with a richer input. Translating them one-to-one carries the workarounds into a system that does not need them.
Common mistakes to avoid
- Estimating the migration from the endpoint count.
- Generating a resolver per endpoint and discovering the N+1 problem in load testing.
- Carrying over denormalised convenience endpoints as queries.
- Exposing the REST error shape through GraphQL instead of designing error handling for the new API.
- Running both APIs indefinitely with no plan to retire one, which doubles the maintenance surface permanently.
How to do it with REST to GraphQL Mapper
The REST to GraphQL Mapper produces the mechanical half of the schema.
- List your endpoints with method and path.
- Generate the draft schema of queries, mutations and types.
- Add the relationships between types by hand — that is the part worth designing.
- Plan batching in the resolver layer before implementing anything.
The GraphQL schema design documentation covers types and relationships. Other developer tools are in the tools directory.
Frequently asked questions
Can a REST API be converted to GraphQL automatically?
The endpoint list gives you queries, mutations and types, which is the mechanical half. The relationships between types — the actual graph — are not expressed anywhere in a path list and have to be designed.
What is the N+1 problem?
Resolving each field with its own backend call, so fetching fifty items with a nested field makes fifty-one requests. It is solved with batching in the resolver layer, not in the schema.
Should every REST endpoint become a field?
No. Bulk fetches and denormalised view endpoints exist to work around REST’s shape, and a well-designed graph makes them unnecessary. Translating them one-to-one carries the workaround forward.
Final thought
Map the endpoints in an afternoon, then spend the real time on the edges between types. The endpoints are the part a tool can do; the graph is the part that decides whether the migration was worth it.