Developing GraphQL API for Mobile Application
GraphQL changes client-server interaction model: instead of fixed endpoints, client declares exactly what fields it needs and gets exactly them. For mobile this is especially valuable — product card screen requests only name, price, thumbnail, not pulling description, reviews, inventory as with REST. On slow connections the difference is noticeable.
When GraphQL Justified for Mobile
GraphQL adds complexity: server implementation (resolvers, schema, DataLoader), client library and team training needed. Justified scenarios:
- Different clients (iOS, Android, Web) need to serve from one API, their data requirements diverge significantly
- Actively changing UI — can add fields to request without server changes
- Nested data with variable depth (social graph, catalog with categories)
For CRUD with predictable data structure, REST is simpler. GraphQL isn't a silver bullet.
Apollo Client on Android
Apollo Kotlin — de facto standard. Generates type-safe request classes from .graphql files at build time.
# app/src/main/graphql/GetProduct.graphql
query GetProduct($id: ID!) {
product(id: $id) {
id
name
price
thumbnail {
url
width
height
}
}
}
// auto-generated GetProductQuery.Data type
val response = apolloClient.query(GetProductQuery(id = productId)).execute()
val product = response.data?.product
apolloClient configured once with HttpEngine, auth headers and cache:
val apolloClient = ApolloClient.Builder()
.serverUrl("https://api.example.com/graphql")
.addHttpHeader("Authorization", "Bearer $token")
.normalizedCache(MemoryCacheFactory(maxSizeBytes = 10 * 1024 * 1024))
.build()
normalizedCache — normalized cache by id field. Product query from feed and detail page returns one object in memory — update in one place reflects everywhere automatically.
Apollo Client on iOS
Apollo iOS generates Swift code similarly to Kotlin.
let client = ApolloClient(
networkTransport: RequestChainNetworkTransport(
interceptorProvider: DefaultInterceptorProvider(store: store),
endpointURL: URL(string: "https://api.example.com/graphql")!
),
store: store
)
client.fetch(query: GetProductQuery(id: productId)) { result in
switch result {
case .success(let response):
let product = response.data?.product
case .failure(let error):
print(error)
}
}
Subscriptions for Real-time
GraphQL subscriptions — WebSocket channel for real-time updates. Perfect for chats, live price updates, order statuses:
subscription OnOrderStatusChanged($orderId: ID!) {
orderStatusChanged(orderId: $orderId) {
status
updatedAt
}
}
Apollo Kotlin supports subscriptions via WebSocketNetworkTransport:
apolloClient.subscription(OnOrderStatusChangedSubscription(orderId = id))
.toFlow()
.collect { response ->
val status = response.data?.orderStatusChanged?.status
}
Optimization: Persisted Queries
Each GraphQL query sends full query text in request body — overhead. Automatic Persisted Queries (APQ): client sends SHA256 hash, server returns data if knows hash, otherwise asks for full text. Apollo Client supports APQ out of box.
N+1 and DataLoader on Server
Classic GraphQL resolver problem: requesting 100 products triggers 100 separate SQL queries to load category for each. Solution — DataLoader (batch + cache): all category requests in one iteration batch into single SELECT ... WHERE id IN (...).
Without DataLoader, schema working fine on 10 objects degrades on 1000. Not client problem, but design at API development stage.
Error Handling
GraphQL returns HTTP 200 even on errors — errors in response body:
{
"data": { "product": null },
"errors": [{ "message": "Product not found", "extensions": { "code": "NOT_FOUND" } }]
}
Client must check errors array independent of HTTP status. Apollo Client provides GraphQLError list in response.errors.
What's Included
Design GraphQL schema for mobile requirements, implement resolvers with DataLoader, configure Apollo Client per platform with cache and subscriptions, integrate authentication via HTTP headers and WebSocket connection params.
Timeline: 2–4 weeks depending on schema scope and backend necessity.







