Software Engineering
Why We Migrated from REST to GraphQL for Our Travel API
The trade-offs, challenges, and benefits of moving a production travel API to GraphQL.
4 min readVeetek team
Our first public API for partners was a conventional REST API. It had endpoints for searches, bookings, reservations, agencies and invoices, and it worked. Over time, though, the requests from partners started to follow a pattern. One wanted reservations with passengers and segments but without payments. Another wanted reservations with payments and invoices but without passenger details. A mobile client wanted a small summary for list views and full detail only on tap.
Each request led to a new endpoint, a new query parameter or a larger response that most clients did not need. We decided to move the partner API to GraphQL. This article explains why, what was harder than expected and what we would do differently.
The problems with our REST API
The REST design itself was reasonable. The problems came from the shape of travel data. A single reservation can include several passengers, flight segments from different suppliers, hotel stays, insurance, payments, invoices and a history of changes. Clients rarely need all of it at once, and they rarely need the same subset.
Over-fetching: list endpoints returned full reservation objects, making responses large and slow on mobile connections.
Under-fetching: building one screen sometimes required four or five sequential calls.
Endpoint sprawl: variants such as include parameters and summary endpoints multiplied and were hard to document consistently.
GraphQL addresses all three by letting the client ask for exactly the fields and relations it needs in one request, described by a typed schema.
How we approached the migration
We did not rewrite everything at once. We built the GraphQL layer on top of the same domain services the REST API used, using Lighthouse for Laravel. Business rules for pricing, permissions and supplier handling stayed in one place, and both APIs called into them.
We started with read operations, since they had the clearest benefit and the lowest risk. Reservations, agencies, invoices and wallet balances came first. Mutations for creating and changing bookings came later, once we were confident in authorisation and error handling.
The REST API stayed available throughout, with a published deprecation timeline so partners could move at their own pace.
What was harder, and what improved
Three areas took more work than we planned.
The first was query cost. A flexible API lets clients request deeply nested data, for example every agency, with every reservation, with every passenger and every payment. Without limits, a single query can load a large part of the database. We added maximum depth and complexity limits, required pagination on every list field and used batched loading to avoid N+1 queries inside resolvers.
The second was authorisation. In REST, permissions were checked per endpoint. In GraphQL, a single query can touch many types, so checks have to happen at the field and type level. An agency user can see their own reservations and net prices, but not another agency's markups. We moved these rules into Laravel policies and applied them consistently in resolvers.
The third was errors. Travel APIs fail in partial ways. A supplier might confirm the flight but reject the seat request. GraphQL allows partial data with errors, which is useful, but clients need to understand which part failed. We defined structured error codes and documented how each mutation reports partial success.
Once in place, the GraphQL API reduced the number of requests clients made to build common screens, and response sizes dropped for list views because clients asked only for what they displayed. The typed schema became our documentation, and partners could explore it with standard tools instead of waiting for us to describe a new endpoint.
It also changed how our own team worked. New front-end features rarely needed new backend endpoints, because the data was already available through the schema. Backend work shifted to adding well-defined types and fields.
When REST is still the better choice
GraphQL is not the right answer for everything, and we kept REST in a few places.
Webhooks to partners, such as booking confirmations, stay as simple HTTP posts with JSON payloads.
File downloads, such as invoices and tickets in PDF, are plain REST endpoints.
Very simple integrations, for example a partner that only needs to check one booking status, are easier with a single REST call.
Caching is also simpler with REST because responses map to URLs. With GraphQL we rely on application-level caching inside resolvers rather than HTTP caching.
Lessons for other teams
If your API serves clients with varied needs and your data is deeply related, GraphQL is worth considering. Build it on top of your existing domain logic, start with reads, set query limits from day one and treat authorisation as a field-level concern. Keep REST where it is simpler. The goal is an API that is easier for partners to use, not a change of technology for its own sake.