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.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
GraphQL Tutorial for Server-Side Swift with Vapor: Getting Started
30 mins
- Getting Started
- How GraphQL Differs From REST
- Declaring Model Types
- GraphQL Fields, Queries and Mutations
- Defining GraphQL Queries
- Exploring the GraphQL API
- Defining GraphQL Mutations
- Adding Pagination to the GraphQL Schema
- Adding a Related Model Type
- Handling Parent-Child Relationships in GraphQL
- Where to Go From Here?
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:
- The new
getAllShows
serves a response to an instance ofRequest
, whichVapor
declares. -
db
lets you access the database, and the staticShow.query
withall()
on top of it fetches all shows. - The second
arguments
parameter of typeNoArguments
fromGraphiti
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)
}
}
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.
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:
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.