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
You are currently viewing page 2 of 4 of this article. Click here to view the first page.

GraphQL Fields, Queries and Mutations

After declaring the model types for your server’s database, you still have to define a GraphQL schema. Any GraphQL schema contains at least a few types that roughly correspond to your database types. Here’s a declaration in the GraphQL schema language for Show:

type Show {
  id: ID
  title: String!
  releaseYear: Int!
}

The declaration above introduces a new type with the type keyword. It also specifies three fields that take no arguments and return values of corresponding types. Fields without arguments almost read like property declarations in Swift, but without the let keyword.

GraphQL also supports optional values. While in Swift you have to explicitly specify a type is optional with a question mark, in GraphQL you explicitly specify a type is non-optional with an exclamation mark. Thus, in this notation ID is optional, while String! is not.

GraphQL makes a distinction between two types of requests: queries and mutations. Queries are read-only requests that aren’t supposed to modify anything on a server. In contrast, mutations mutate the state of the server by definition.

These requests have actual types that specify queries and mutations as their fields. Here’s how it looks:

type Query {
  shows: [Show!]!
}

type Mutation {
  createShow(title: String! releaseYear: Int!): Show!
  deleteShow(id: UUID!): Boolean!
  updateShow(id: UUID! releaseYear: Int! title: String!): Boolean!
}

This reads as a single shows query that returns a non-optional array of non-optional Show values. At the same time, this schema declares three mutations, each taking non-optional arguments. The createShow mutation returns a newly created non-optional Show value, while deleteShow and updateShow return a non-optional boolean value that indicates whether a mutation succeeded.

With that out of the way, you can move on to defining GraphQL queries.

Defining GraphQL Queries

In this tutorial, instead of using the GraphQL schema language directly, you’ll rely on a domain-specific language from the Graphiti library. This lets you declare fields, queries and mutations in Swift in a type-safe way. Actual code that returns data for a given field is a resolver, which you’ll declare on a new Resolver class.

In App, create a new subdirectory called GraphQL. Next, create new file named Resolver.swift and replace its contents with the following:

import Graphiti
import Vapor

final class Resolver {
  func getAllShows(
    request: Request,
    arguments: NoArguments
  ) throws -> EventLoopFuture<[Show]> {
    Show.query(on: request.db).all()
  }
}

Here’s a code breakdown:

  1. The new getAllShows serves a response to an instance of Request, which Vapor declares.
  2. db lets you access the database, and the static Show.query with all() on top of it fetches all shows.
  3. The second arguments parameter of type NoArguments from Graphiti explicitly indicates that this field takes no arguments.

Now, in GraphQL, create a file named Schema.swift. Add this schema definition:

import Graphiti
import Vapor

let schema = try! Schema<Resolver, Request> {
  Scalar(UUID.self)

  Type(Show.self) {
    Field("id", at: \.id)
    Field("title", at: \.title)
    Field("releaseYear", at: \.releaseYear)
  }

  Query {
    Field("shows", at: Resolver.getAllShows)
  }
}
Note: Since UUID is not a built-in type in GraphQL, you have to declare it separately in the schema as a Scalar. The Type and Query blocks declare the Show type and the shows query respectively.

Great! You defined your first type and query in a GraphQL Schema using Server-side Swift. Remember, this schema serves as a blueprint for your API. Any apps consuming the API can essentially share the schema, so your frontend and backend are on the same page.

Next, go to configure.swift and add this line right above the GraphiQL setup code, but below the migration code:

app.register(graphQLSchema: schema, withResolver: Resolver())

This line registers the GraphQL schema with your application, using your Resolver to allow access to Show‘s queries.

Next, you’ll explore the GraphQL API.

Exploring the GraphQL API

You’re going to explore your GraphQL schema using your browser. Build and run the project to see your first working GraphQL schema. Refresh the browser tab pointing to http://127.0.0.1:8080 and type your first query in the query editor:

query {
  shows {
    id
    title
  }
}

Press Cmd+Enter or click the button with the Run triangle at the top left corner of GraphiQL UI. You’ll see an empty result to the right, which is to be expected from an empty database.

The GraphiQL client page with no results in an empty database for the shows query

Notice the Docs navigation button in the top right corner highlighted with a red oval on the screenshot above. Clicking it displays interactive documentation for your schema, which you’ll find quite handy when working with any non-trivial GraphQL API.

You can verify your API in terminal with the cURL HTTP client like this:

curl -X "POST" "http://127.0.0.1:8080/graphql" -H 'Content-Type: application/json' -d $'{
  "query": "query { shows { id title } }",
  "variables": {}
}'

Here you issue a simple POST request to http://127.0.0.1:8080/graphql, which is the entry point for the API. The request body contains a JSON object with only two fields: query with the query body and variables for parameterized queries or mutations that you’ll learn about later.

Now you’ll learn how to define GraphQL mutations.

Defining GraphQL Mutations

To fill the database with data you need to define appropriate mutations in the schema. Open Resolver.swift and add these struct declarations and functions to the body of Resolver:

struct CreateShowArguments: Codable {
  let title: String
  let releaseYear: Int
}

func createShow(
  request: Request,
  arguments: CreateShowArguments
) throws -> EventLoopFuture<Show> {
  let show = Show(
    title: arguments.title,
    releaseYear: arguments.releaseYear
  )
  return show.create(on: request.db).map { show }
}

struct UpdateShowArguments: Codable {
  let id: UUID
  let title: String
  let releaseYear: Int
}

func updateShow(
  request: Request,
  arguments: UpdateShowArguments
) throws -> EventLoopFuture<Bool> {
  Show.find(arguments.id, on: request.db)
    .unwrap(or: Abort(.notFound))
    .flatMap { (show: Show) -> EventLoopFuture<()> in
      show.title = arguments.title
      show.releaseYear = arguments.releaseYear
      return show.update(on: request.db)
    }
    .transform(to: true)
}

struct DeleteShowArguments: Codable {
  let id: UUID
}

func deleteShow(
  request: Request,
  arguments: DeleteShowArguments
) -> EventLoopFuture<Bool> {
  Show.find(arguments.id, on: request.db)
    .unwrap(or: Abort(.notFound))
    .flatMap { $0.delete(on: request.db) }
    .transform(to: true)
}

Each function here runs a database statement that takes arguments passed in a corresponding Codable structure. Check out the documentation in the Fluent module for more details about these database functions.

Next, open Schema.swift and add the following mutation definition below the Query:

Mutation {
  Field("createShow", at: Resolver.createShow) {
    Argument("title", at: \.title)
    Argument("releaseYear", at: \.releaseYear)
  }

  Field("updateShow", at: Resolver.updateShow) {
    Argument("id", at: \.id)
    Argument("title", at: \.title)
    Argument("releaseYear", at: \.releaseYear)
  }

  Field("deleteShow", at: Resolver.deleteShow) {
    Argument("id", at: \.id)
  }
}

Notice how this Mutation is different from the Query. Every Field here requires explicit arguments with their names. In addition, they need keypaths to properties of Arguments you’ve previously declared in the Resolver body.

Build and run. Then refresh the GraphiQL page. Now run createShow to add a new show:

mutation {
  createShow(
    title: "The Chilling Adventures of RESTful Heroes"
    releaseYear: 2020
  ) {
    id
  }
}

Now you’ll see the newly created shoe’s UUID in the response:

Results of the createShow mutation displayed in the GraphiQL client

Notice that the documentation explorer in the rightmost part of the window displays all of the newly defined mutations. This is one of the benefits of integrated GraphQL tooling: You get nicely formatted and highlighted API documentation regenerated in real-time as you go along. How’s about that, RESTful Heroes? :]

Make sure you also try updateShow and deleteShow, which, you guessed it right, update and delete existing shows.

Next, you’ll add pagination to the GraphQL schema.

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.