Introduction: REST vs GraphQL — an old battle in the new world of Swift
Once upon a time, in the era of Objective-C and selector-based dispatch, the choice between REST and GraphQL was not so acute. REST was king, and JSON was its faithful squire. But times have changed. Swift has become a language with a strong type system, async/await, Codable, and powerful frameworks like Apollo iOS have emerged. Now the question "REST or GraphQL?" sounds different: "What will bring more benefit to my Swift project?"
In this article, we will not retell Wikipedia. We will take a real example of an iOS application for an educational platform (yes, that very PythonLib) and see how each approach behaves in Swift code. You will learn when REST still rules and when GraphQL is a true savior.
Briefly about the main thing: how REST differs from GraphQL
For those just getting into the topic — basic concepts.
- REST is an architectural style where each resource (user, course, lesson) has its own URL. You get a fixed data structure that the server decided to give you. Often — a whole bunch of extra stuff.
- GraphQL is a query language. You decide which fields you need. One entry point (
/graphql) and one query can replace several REST queries.
On paper, GraphQL looks like a silver bullet. But in Swift projects, there are nuances that we will now examine in practice.
Example 1: Getting a user profile and their courses
REST approach in Swift (URLSession + Codable)
Let's say we have a classic REST API. To show the profile and list of courses, two requests are needed:
// Modelsstruct UserProfile: Codable { let id: Int let name: String let email: String let avatarURL: String}
struct Course: Codable { let id: Int let title: String let progress: Double}
// Network layerfunc fetchUserProfile(userId: Int) async throws -> UserProfile { let url = URL(string: "https://api.pythonlib.ru/users/\\(userId)")! let (data, _) = try await URLSession.shared.data(from: url) return try JSONDecoder().decode(UserProfile.self, from: data)}
func fetchCourses(userId: Int) async throws -> [Course] { let url = URL(string: "https://api.pythonlib.ru/users/\\(userId)/courses")! let (data, _) = try await URLSession.shared.data(from: url) return try JSONDecoder().decode([Course].self, from: data)}
// Usagelet profile = try await fetchUserProfile(userId: 42)let courses = try await fetchCourses(userId: 42)Everything is simple and predictable. But there is a problem: we get all profile fields, even if we only need the name and email. And also — two network requests. On a slow connection, this is painful.
GraphQL approach in Swift (Apollo iOS)
Now the same thing, but via GraphQL. First, define the query in a .graphql file:
query GetUserWithCourses($userId: ID!) { user(id: $userId) { id name email courses { id title progress } }}Apollo generates typed Swift code automatically. In Xcode it looks like this:
import Apollo
let apolloClient = ApolloClient(url: URL(string: "https://api.pythonlib.ru/graphql")!)
func fetchUserWithCourses(userId: Int) async throws -> GetUserWithCoursesQuery.Data { return try await withCheckedThrowingContinuation { continuation in apolloClient.fetch(query: GetUserWithCoursesQuery(userId: userId)) { result in switch result { case .success(let graphQLResult): if let data = graphQLResult.data { continuation.resume(returning: data) } else if let errors = graphQLResult.errors {