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?
Adding Pagination to the GraphQL Schema
Now that you can have multiple shows in your database and know how to handle GraphQL fields with arguments, it’s time to support pagination. Pagination lets you perform queries that return a limited number of results and keep track of where you left off, so you can request the next set.
Open Resolver.swift, and add this type declaration to the top of file above the definition for Resolver:
struct PaginationArguments: Codable {
  let limit: Int
  let offset: Int
}
Next, modify getAllShows in the body of Resolver to pass values of this new struct’s properties to limit and offset query modifiers like this:
func getAllShows(
  request: Request,
  arguments: PaginationArguments
) throws -> EventLoopFuture<[Show]> {
  Show.query(on: request.db)
    .limit(arguments.limit)
    .offset(arguments.offset)
    .all()
}
Now you have to update the GraphQL schema so it passes the new arguments to the resolver function. Navigate to Schema.swift and replace the Query in it with:
Query {
  Field("shows", at: Resolver.getAllShows) {
    Argument("limit", at: \.limit)
    Argument("offset", at: \.offset)
  }
}
Build and run. Then navigate to the GraphiQL client and click History in the top left panel:
As the project currently uses an in-memory database, restarting the process will automatically clean it up. Going through history lets you quickly select a createShow mutation to populate the database again. Add a couple of shows to the database and run this query:
query {
  shows(limit: 10, offset: 0) {
    id
    title
  }
}
Verify that the shows you initially created display correctly. You can also play with different limit and offset arguments to see differently filtered pagination results.
Now it’s time to add a related model type.
Adding a Related Model Type
You’ve worked with shows, but what about actual reviews? Fear not! Adding a related model type is similar to what you’ve seen so far and only requires a special property wrapper to link the types.
In Models, create a new file named Review.swift and add the following:
import Fluent
import Vapor
final class Review: Model, Content {
  static let schema = "reviews"
  @ID(key: .id)
  var id: UUID?
  @Field(key: "title")
  var title: String
  @Field(key: "text")
  var text: String
  @Parent(key: "show_id")
  var show: Show
  init() { }
  init(id: UUID? = nil, showID: UUID, title: String, text: String) {
    self.id = id
    self.$show.id = showID
    self.title = title
    self.text = text
  }
}
Next, open Show.swift and add a new field to Show below the rest of the field:
@Children(for: \.$show)
var reviews: [Review]
The @Parent and @Children property wrappers define the corresponding fields of the Parent and Child types. With the Parent and Child models defined, your two models are now appropriately linked.
You also need to declare resolver functions to actually fetch these reviews in relation to a given show. Add this extension to the bottom of the Show.swift:
extension Show {
  func getReviews(
    request: Request,
    arguments: PaginationArguments
  ) throws -> EventLoopFuture<[Review]> {
    $reviews.query(on: request.db)
      .limit(arguments.limit)
      .offset(arguments.offset)
      .all()
  }
}
Then in Migrations, create a new file named MigrateReviews.swift and add the following:
import Fluent
struct MigrateReviews: Migration {
  func prepare(on database: Database) -> EventLoopFuture<Void> {
    return database.schema("reviews")
      .id()
      .field("title", .string, .required)
      .field("text", .string, .required)
      .field("show_id", .uuid, .required, .references("shows", "id"))
      .create()
  }
  func revert(on database: Database) -> EventLoopFuture<Void> {
    return database.schema("reviews").delete()
  }
}
This barely differs from the MigrateShows type you declared previously. The only significant difference is the .references("shows", "id") argument passed for the relation show_id field.
Next, open configure.swift and add the following migration to configure below the line migrating MigrateShows:
app.migrations.add(MigrateReviews())
Next, you’ll explore how to handle parent-child relationships in GraphQL.
Handling Parent-Child Relationships in GraphQL
The new Review type needs function definitions in Resolver.swift, too. 
Add these CRUD functions at the bottom of Resolver‘s body, right below the existing Show resolvers:
func getAllReviews(
  request: Request,
  arguments: PaginationArguments
) throws -> EventLoopFuture<[Review]> {
  Review.query(on: request.db)
    .limit(arguments.limit)
    .offset(arguments.offset)
    .all()
}
struct CreateReviewArguments: Codable {
  let showID: UUID
  let title: String
  let text: String
}
func createReview(
  request: Request,
  arguments: CreateReviewArguments
) throws -> EventLoopFuture<Review> {
  let review = Review(
    showID: arguments.showID,
    title: arguments.title,
    text: arguments.text
  )
  return review.create(on: request.db).map { review }
}
struct UpdateReviewArguments: Codable {
  let id: UUID
  let title: String
  let text: String
}
func updateReview(
  request: Request,
  arguments: UpdateReviewArguments
) throws -> EventLoopFuture<Bool> {
  Review.find(arguments.id, on: request.db)
    .unwrap(or: Abort(.notFound))
    .flatMap { (review: Review) -> EventLoopFuture<()> in
      review.title = arguments.title
      review.text = arguments.text
      return review.update(on: request.db)
    }
    .transform(to: true)
}
struct DeleteReviewArguments: Codable {
  let id: UUID
}
func deleteReview(
  request: Request,
  arguments: DeleteReviewArguments
) -> EventLoopFuture<Bool> {
  Review.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 to perform the CRUD operations on Review‘s.
Now you need to update the schema in Schema.swift. In Schema.swift, add a new Type declaration just above the existing one for Type(Show.self):
Type(Review.self) {
  Field("id", at: \.id)
  Field("title", at: \.title)
  Field("text", at: \.text)
}
The existing Show schema also needs modifications to take advantage of the new extension. Add a new reviews field to the bottom of this Type:
Field("reviews", at: Show.getReviews) {
  Argument("limit", at: \.limit)
  Argument("offset", at: \.offset)
}
This is not a restriction of GraphQL itself but a technical limitation of Graphiti.
Type blocks. The Graphiti library builds the schema in the same order you specify its types. Since the Show GraphQL type references the Review type through the reviews field, you have to put the Type(Review.self) block above the Type(Show.self) block in the schema definition. 
This is not a restriction of GraphQL itself but a technical limitation of Graphiti.
Now update the Query block by adding a new reviews  below shows:
Field("reviews", at: Resolver.getAllReviews) {
  Argument("limit", at: \.limit)
  Argument("offset", at: \.offset)
}
Note these last two fields are almost identical, but use two different functions to get their results. The first reviews field on the Show type fetches reviews for that specific show with the Show.getReview you’ve defined in the extension. The second field definition uses Resolver.getAllReviews, which fetches all reviews across the whole database.
Finally, add remaining mutations at the bottom of Mutation:
Field("createReview", at: Resolver.createReview) {
  Argument("showID", at: \.showID)
  Argument("title", at: \.title)
  Argument("text", at: \.text)
}
Field("updateReview", at: Resolver.updateReview) {
  Argument("id", at: \.id)
  Argument("title", at: \.title)
  Argument("text", at: \.text)
}
Field("deleteReview", at: Resolver.deleteReview) {
  Argument("id", at: \.id)
}
Build and run. Then refresh the browser tab with the GraphiQL client. As expected, new queries, fields and mutations appear in the documentation explorer.
Add a few shows to the database and note their identifiers. You can then pass them to the new createReview mutation to submit reviews.
When you have a few instances of each type in the database, you can compose deep queries that fetch fields across relations. Try this one, which fetches a maximum of ten shows and ten reviews from each show:
query {
  shows(limit: 10, offset: 0) {
    id
    title
    reviews(limit:10 offset: 0) {
      id
      title
      text
    }
  }
}
Congratulations, you have a fully functional GraphQL API server that can fetch entities of different types and paginate across relationships between them!



