GraphQL with Apollo CLI on iOS: What ChatGPT and the literature don’t tell you

GraphQL with Apollo CLI

If we ask ChatGPT what GraphQL is, the answer is: ‘GraphQL is an API query language developed by Facebook. It is described as a revolutionary way of managing client-server communication, allowing the client to request only the necessary data and receive structured responses efficiently.

GraphQL has in fact been adopted not only by Facebook but also by giants such as Airbnb, Atlassian, Shopify, Netflix and PayPal. The core principle is that GraphQL calls can offer better performance than REST calls, especially when it is necessary to combine several structured data.

But how is GraphQL developed in the iOS environment? Is it really as efficient as described?

Apollo CLI on iOS

Apollo CLI facilitates the integration of GraphQL in iOS apps, offering tools for query and data management. Strong typing and cache management make Apollo valuable in development.

The Apollo CLI allows automatic generation of classes, mutations and queries directly from the GraphQL schema. It is available via CocoaPods or Swift Package Manager (SPM). However, the official documentation does not mention some problems that may come up during configuration.

Configuration problems

To install the Apollo CLI, we followed the official instructions carefully, testing both via SPM and the plugin offered for Xcode(v15.3), but the Apollo CLI executable was not included in the project. This prevented the automatic generation of GraphQL models.

Instead, when using CocoaPods, the installation was successful. Nevertheless, the technical choice was to use SPM exclusively.

Consulting online forums, the solutions proposed were:

  • Downgrade Xcode
  • Manually importing the Apollo CLI executable (solution adopted)

JSON configuration

The creation of the JSON configuration file did not cause any problems.

Generation command:

apollo-ios-cli init --schema-namespace <namespace>

--module-type <type> [--target-name <target name>]

Example of generated JSON configuration:

{

  "schemaNamespace" : "GraphQLProject",

  "input" : {

    "operationSearchPaths" : ["**/*.graphql"],

    "schemaSearchPaths" : ["**/*.graphqls"]

  },

  "output" : {

    "testMocks" : { "none" : {} },

    "schemaTypes" : {

      "moduleType": { "swiftPackageManager": {} },

      "path": "./GraphQLSchemas"

    },

    "operations" : { "inSchemaModule" : {} }

  }

}

This file allows you to configure the properties that will handle the code generated by the Apollo CLI. The GraphQL schema can be downloaded directly from a remote URL or defined in local files.

Difference between .graphqls and .graphql files

  • .graphqls: Contains the definition of the server-side data structure (entities, enums, interfaces, queries, mutations).
  • .graphql: Contains the client-defined queries and mutations.

GraphQL allows the client to request only the necessary data, avoiding the retrieval of unnecessary information unlike the REST API.

Output Management

The output configuration allows you to decide how to generate the packet:

  • Embedded in Target
  • Swift Package Manager (SPM)
  • Other

The choice depends on the project architecture. In a whitelabel app with multiple targets, the solution adopted by us was an SPM package that could be imported with Tuist support.

Query management

Every query defined in .graphql must have a correspondent in the schema. In an ongoing project, it is essential to maintain control over the queries requested and the schema. If a field changes name or is removed, Apollo CLI stops pattern generation, reporting the error with the row number.

How to reduce errors? By using Fragments.

Example without Fragments:

query GetBookById($getBookById: ID!) {

  getBookById(bookId: $bookId) {

    id

    title

  }

}

query GetBookByCode($code: Int) {

  getBookByCode(code: $code) {

    id

    title

    type

    code

    author

  }

}

If the backend changes the code field to codeId, we will have to update both queries.

Example withFragments:

fragment BookFragment on Book {

  id

  title

  type

  code

  author

}

query GetBookById($getBookById: ID!) {

  getBookById(bookId: $bookId) {

    ...BookFragment

  }

}

query GetBookByCode($code: Int) {

  getBookByCode(code: $code) {

    …BookFragment

  }

}

If code changes in codeId, we will only need to update BookFragment, avoiding ripetead errors.

However, excessive use of fragments may lead to overly generic queries, compromising the efficiency of requests.

Apollo CLI problems with multiple files

If definitions are split into multiple .graphql files, the Apollo CLI may not recognise fragments defined in a separate file, generating errors.

ChatGPT suggests using:

apollo codegen:generate --target=swift

--includes=GraphQL/**/*.graphql

--localSchemaFile=schema.graphql --output=GraphQLAPI.swift

Here is the test to see if this solution really works.

Generated Swift code

import Apollo

class BookService {

    private let client = ApolloClient(url: URL(string: "[BASEURL]")!)

    func fetchBooks(completion: @escaping (Result<[Book], Error>) -> Void) {

        client.fetch(query: GetBooksQuery()) { result in

            switch result {

            case .success(let graphQLResult):

                if let books = graphQLResult.data?.books {

                    completion(.success(books.map { $0.fragments.bookFragment }))

                } else if let errors = graphQLResult.errors {

                    completion(.failure(errors.first!))

                }

            case .failure(let error):

                completion(.failure(error))

            }

        }

    }

}

Apollo generates a separate class for each query, even though the objects are the same. To keep the code more modular, the only way is to use fragments.

Cache Management and Errors

Apollo offers advanced cache management, allowing you to define the behaviour for each call. It is useful to note that the cache is active by default, which may prevent requests from being displayed in debug logs if monitoring libraries are used. However, within fetch or mutation, you can check whether the data has been retrieved from the cache.

Error management is robust and customisable, making it easy to detect mismatches between schema and query.

Conclusions

GraphQL’s integration with Apollo on iOS offers a powerful tool for client-server communication, combining efficiency and typing. However, in order to achieve clean and scalable code, it is essential to structure queries correctly and make the most of fragments

Once its operation is understood, Apollo CLI can become a viable alternative to the REST API.


Main Author: Samantha Giro, Technical Mobile Lead

Do you want to know more about our services? Fill in the form and schedule a meeting with our team!