Working with SwiftGraphQL and Hygraph

Swift GraphQL is a popular library for working with GraphQL in Swift. We'll explore how use Swift GraphQL and Hygraph in tandem in this post.

Craig Tweedy
Craig Tweedy
Working with GraphQL, Hygraph, Swift, and GraphQL Swift

Swift serves as the backbone for iOS applications. As more software comes to phone applications, working with GraphQL endpoints in Swift becomes more and more important. Many libraries have popped up to aid in this effort, such as Swift GraphQL which we'll be exploring today.

Swift GraphQL is a package by The Guild that lets you write GraphQL queries using the native Swift language, and it’ll take care of the rest. Swift GraphQL provides a CLI tool which can convert a GraphQL schema into native Swift. This provides you with type safety in your queries and mutations preventing you from shipping broken apps, as any invalid queries will automatically fail a build. Checks could be added to your CI tooling, allowing you to compare and analyse whether your schema has changed.

In this example we’ll get Swift GraphQL set up, with the appropriate services in our application, and use this to request products from Hygraph and display them in a list. We’ll explore how Swift GraphQL makes selections and queries, and how it converts API data into local Swift data models.

Instead of creating a Hygraph project from scratch to follow along, you can use the endpoint https://api-eu-central-1.hygraph.com/v2/ck8sn5tnf01gc01z89dbc7s0o/master. All of the Hygraph examples repo on Github use this endpoint, and no authentication is required.

Getting StartedAnchor

In XCode, let's create a new project. Select the multiplatform app option, and provide your project a name and organisation identifier.

Generating our SchemaAnchor

Swift GraphQL offers a CLI to automatically create a schema from a GraphQL endpoint.

Follow the instructions listed to create a Swift file, which is a representation of your schema.

You may have to provide type mappings in a SwiftGraphQL.yml config to correctly handle custom types, such as dates. For example, here is a DateTime structure for Hygraph:

# swiftgraphql.yml
scalars:
DateTime: DateTimeScalar
import Foundation
import SwiftGraphQL
struct DateTimeScalar: Codec {
private var data: Date
var raw: Int
init(from date: Date) {
data = date
raw = Int(date.timeIntervalSince1970)
}
// MARK: - Public interface
var value: String {
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "fr")
formatter.setLocalizedDateFormatFromTemplate("dd-MM-yyyy")
return formatter.string(from: data)
}
// MARK: - Codec conformance
// MARK: - Decoder
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let value = try container.decode(Int.self)
data = Date(timeIntervalSince1970: TimeInterval(value))
raw = value
}
// MARK: - Encoder
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(Int(data.timeIntervalSince1970))
}
// MARK: - Mock value
static var mockValue = DateTimeScalar(from: Date())
}

Once you have your schema generated, add it to the project by dragging it into the file pane.

Creating our GraphQLAPIAnchor

Now that we have our schema generated, we can create an API class to handle the schema operations.

Create a GraphQLAPI file, and fill it with:

import Foundation
import SwiftGraphQL
class GraphQLAPI {
func performOperation<T>(query: Selection.Query<T>) async throws -> T {
try await withCheckedThrowingContinuation { continuation in
SwiftGraphQL.send(query, to: "endpoint") { result in
if let data = try? result.get() {
continuation.resume(returning: data.data)
}
}
}
}
}

This class handles performing GraphQL operations using Selection.Query and send provided by SwiftGraphQL. We also wrap this with withCheckedThrowingContinuation in order to provide async/await support to our application.

Retrieving ProductsAnchor

Now that we've got the prep work done, we can start retrieving our products. We'll first need our product model:

struct Product: Decodable, Identifiable {
var id: String = UUID().uuidString
let name: String
let description: String
let price: Int
}

Next, we'll need to use SwiftGraphQL to create a selection query. Let's create a ProductsQuery file to hold this selection:

import Foundation
import SwiftGraphQL
class ProductsQuery {
static var LIST_PRODUCTS: Selection<[Product], Objects.Query> {
Selection.Query {
return try $0.products(
stage: .published,
locales: [.en],
selection: Selection.list(
Selection.Product {
Product(
id: try $0.id(),
name: try $0.name(),
description: try ($0.description() ?? ""),
price: try $0.price()
)
}
)
)
}
}
}

This creates a selection for products which are only published in our en locale, and prepares the selection to map to a list of products as defined by the model we've just created.

Finally, let's make a function to call the API using our operation. For convenience, we will create a APIService class to handle this:

class APIService {
let api: GraphQLAPI = GraphQLAPI()
func listProducts() async -> [Product] {
return (try? await self.api.performOperation(query: ProductsQuery.LIST_PRODUCTS)) ?? []
}
}

This uses our LIST_PRODUCTS query and passes it to our GraphQLAPI's performOperation method. If the operation completes successfully, the API will automatically decode the response, and return the objects provided as the return type listed in our function - in this case [Product] (a list of Product).

By using try?, we can default the return result if the operation does not complete successfully to return an empty list. We could also handle the error here in some other way by just using try with a do/catch.

Displaying ProductsAnchor

Finally, let's display the products.

In our default ContentView (create a new SwiftUI file if you don't have one), we'll need to store our products in the state, by adding to our View structure:

@State var products: [Product] = []

We'll also require a function to retrieve our products using our previously defined function:

func loadProducts() async {
self.products = APIService().listProducts()
}

Finally, let's replace the body with a list and load in the products on view appear: swift List(self.products, id: \.id) { product in Text(product.name) }.onAppear { Task.init { await self.loadProducts() } }

And voila! We should now have a working list of products displaying.

I hope you found this useful, and to learn more about Swift with Hygraph, checkout the example for this project on Github