GraphQL Tutorial for Server-Side Swift with Vapor: Getting Started

For a long time, solving the problem of API integrations between frontend and server-side seemed trivial. You might have stumbled across some HTML form encoding or legacy APIs that relied on SOAP and XML, but most APIs used REST with JSON encoding. While REST looked like the de-facto standard, ironically, it didn’t have a defined […] By Max Desiatov.

Leave a rating/review
Download materials
Save for later
Share

For a long time, solving the problem of API integrations between frontend and server-side seemed trivial. You might have stumbled across some HTML form encoding or legacy APIs that relied on SOAP and XML, but most APIs used REST with JSON encoding.

While REST looked like the de-facto standard, ironically, it didn’t have a defined spec to adhere to. Additionally, common REST patterns don’t work in all situations. Developers needed a clearly defined API specification that multiple libraries in different ecosystems could conform to.

There were multiple attempts to create this specification, but GraphQL gained the most traction. Many public API providers, including GitHub, Shopify, Facebook, Pinterest and Airbnb, adopted GraphQL APIs. With the first spec introduced in 2015, GraphQL has gained client and server implementations for all of the most popular languages, including Swift.

In this tutorial, you’ll get started with GraphQL and Server-Side Swift using a concrete API as an example. With so many TV shows out there, how do you pick the best one without checking reviews first? An API providing a list of available shows and reviews via GraphQL would be a great learning opportunity.

In this tutorial, you’ll build an API server called Fresh Tomatoes. You’ll use Vapor, the most popular server-side Swift framework, and the GraphQLKit library for the API. Along the way, you’ll learn:

  • How GraphQL compares to REST.
  • What fields, queries and mutations are in GraphQL.
  • How to expose your model types and parent-child relationships in a GraphQL API.
  • Pagination in GraphQL.

Getting Started

Download the project materials by clicking the Download Materials button at the top of or bottom of this page. Unpack the downloaded archive, and navigate to starter. Open Package.swift with Xcode and wait until all items in Swift Package Dependencies finish loading:

The starter project opened in Xcode

Click Run in the top-left corner of the screen, as highlighted on the screenshot above. After the build process finishes, you’ll see this message at the bottom of the window in your Xcode console:

[ NOTICE ] Server starting on http://127.0.0.1:8080

Open your favorite browser and navigate to http://127.0.0.1:8080, which will look similar to the following:

The GraphiQL client page in opened in the browser

What you see in the browser is the de-facto standard GraphQL client, GraphiQL. You can specify your GraphQL requests in the editor on the left side. The view on the right displays a corresponding response.

The "error": true response you see on the right is normal for a starter project. You haven’t defined your GraphQL schema yet, so there’s nothing for the server to return, even for an empty request.

You can use any other HTTP client to issue raw GraphQL requests, but you’ll probably find GraphiQL one of the most convenient during development. You’ll learn how to use the cURL HTTP client in a future section when you have a meaningful schema to query.

Before you jump into coding, let’s take a moment to explore how GraphQL differs from REST.

How GraphQL Differs From REST

The fact that REST doesn’t have a defined standard or specification is a significant downside. This leaves API authors with many decisions to make and no way to enforce inconsistencies in numerous implementations. Here are a few questions you may have when developing with REST:

  • How do you implement validation and communicate validation errors to the client?
  • Do pagination and filtering work? If so, how?
  • How do you document and test your API?
  • Is there a way to represent relationships and batch queries?

If you develop server and client code separately, you have to figure out the communication processes, too.

  • How does the server-side team communicate about changes to the existing API?
  • What happens after you deprecate an API or when validation rules change?
  • How do you make sure you don’t forget to reflect these changes in every client app?

Over the years, some solutions emerged within the REST school of thought. At the moment, OpenAPI is the most prominent.

In contrast, GraphQL’s authors tried to avoid these issues from the beginning.

You start developing a GraphQL API with a schema that describes model types and their fields. Usually, you specify it in a specialized but simple GraphQL schema language. Both server and client code interact with each other only using types and fields declared in the schema.

The schema language also supports user-defined types and even documentation comments. With GraphQL’s clear specification, you get an integrated experience with libraries and developer tools that conform to it. You can then store your API schema in a code repository, letting you easily track any changes, as you would with any other code.

Both client and server apps can reuse it, while developer tools can provide advanced features such as code generation, autocomplete and documentation renderers. When you update the schema on the server, your client code automatically validates existing requests against the new schema.

Overall, this lets you iterate faster on both sides of the stack. What’s not to like?

With that out of the way, it’s time to start coding, and where better to start than your models.

Declaring Model Types

Before you start writing your GraphQL schema, you need to add a few simple model types to the server. In the project’s App directory, create a new subdirectory called Models. In this new subdirectory, create a new file named Show.swift and add the following:

import Fluent
import Vapor

final class Show: Model, Content {
  static let schema = "shows"

  @ID(key: .id)
  var id: UUID?

  @Field(key: "title")
  var title: String

  @Field(key: "releaseYear")
  var releaseYear: Int

  init() { }

  init(id: UUID? = nil, title: String, releaseYear: Int) {
    self.id = id
    self.title = title
    self.releaseYear = releaseYear
  }
}

Here you create a Show type that conforms to Model. Your new type declares three fields, of which id with the @ID property wrapper is the most important. The database uses this field to uniquely identify a model instance.

The rest of the fields use @Field, which takes a database key string as an argument. For consistency, you specify the database key string to be the same names of your corresponding properties. You also declare two initializers: One creates a fresh empty model instance, and the other is for an instance with a specified title and release year.

Next, you need to tell the database about your new model. Underneath, the App directory, create Migrations as a sibling of Models. Add a new file named MigrateShows.swift with this code:

import Fluent

struct MigrateShows: Migration {
  func prepare(on database: Database) -> EventLoopFuture<Void> {
    return database.schema("shows")
      .id()
      .field("title", .string, .required)
      .field("releaseYear", .int, .required)
      .create()
  }

  func revert(on database: Database) -> EventLoopFuture<Void> {
    return database.schema("reviews").delete()
  }
}

Here, prepare specifies appropriate types for each field and creates a new database schema with the name shows. revert runs when you need to revert this migration. In this case, deleting the previously created schema is enough.

The database schema and GraphQL schema are separate entities. The former describes how your server stores data, while the latter describes how it communicates with its clients.

Next, open the project’s existing configure.swift and the new migration just above try app.autoMigrate().wait() in configure:

app.migrations.add(MigrateShows())
Note: In this tutorial, you use an in-memory SQLite database, so data won’t persist between runs. That said, feel free to use any other database driver supported by Fluent.

Here’s what your project structure looks like after adding the new files:

The file tree of the project after the MigrateShows.swift file is added

Build the project to verify that new models and migrations compile without any errors.

Next, you’ll take a look at GraphQL fields, queries and mutations.

Max Desiatov

Contributors

Max Desiatov

Author

Kenny Dubroff

Tech Editor

Julia Zinchenko

Illustrator

Darren Ferguson

Final Pass Editor

Tim Condon

Team Lead

Over 300 content creators. Join our team.