Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Server Side Swift and GraphQL: A Match Made In Heaven Or Hell? - Deep Dish Swift, Chicago IL, May 2023

Server Side Swift and GraphQL: A Match Made In Heaven Or Hell? - Deep Dish Swift, Chicago IL, May 2023

I like server side Swift. I also like GraphQL. What happens when you try to put them together? Am I getting chocolate in my peanut butter, or am I creating a monster?

This talk walks through some of the very basics of servers and how GraphQL works, all the way up to running live GraphQL queries on a Vapor server.

Repo: https://github.com/designatednerd/MoveItMoveIt/

Recording: ✨Coming soon! ✨

Ellen Shapiro
PRO

May 02, 2023
Tweet

More Decks by Ellen Shapiro

Other Decks in Technology

Transcript

  1. SERVER-SIDE SWIFT AND GRAPHQL:
    A MATCH MADE IN
    !
    HEAVEN OR HELL ?
    DEEP DISH SWIFT | CHICAGO, IL | MAY 2023
    ELLEN SHAPIRO | MASTODON.SOCIAL/@DESIGNATEDNERD | PIXITEAPPS.COM

    View Slide

  2. FULL
    !
    DISCLOSURE
    @[email protected]

    View Slide

  3. View Slide

  4. View Slide

  5. https://www.flickr.com/photos/fwooper7/9650957891

    View Slide

  6. SOME DEFINITIONS
    @[email protected]

    View Slide

  7. "Docker is a container
    virtualization platform that
    builds on tools like Virtual Box!"
    @[email protected]

    View Slide

  8. View Slide

  9. SOME BASIC DEFINITIONS
    @[email protected]

    View Slide

  10. SOME BASIC DEFINITIONS
    FOR A LOT OF APPS
    @[email protected]

    View Slide

  11. YOUR APP'S DATA
    @[email protected]

    View Slide

  12. YOUR APP'S DATA
    THE INFORMATION YOU WANT TO STORE, MANIPULATE AND/OR DISPLAY
    @[email protected]

    View Slide

  13. THE SERVER
    @[email protected]

    View Slide

  14. THE SERVER
    THE PLACE WHERE THE CANONICAL VERSION OF YOUR DATA LIVES
    @[email protected]

    View Slide

  15. THE SERVER
    THE PLACE WHERE THE CANONICAL VERSION OF YOUR DATA LIVES
    (Eventually)

    View Slide

  16. THE SERVER
    > Some way to store data
    @[email protected]

    View Slide

  17. THE SERVER
    > Some way to store data
    > Some way to fetch data from storage
    @[email protected]

    View Slide

  18. THE SERVER
    > Some way to store data
    > Some way to fetch data from storage
    > Some way to receive requests for data
    @[email protected]

    View Slide

  19. THE SERVER
    > Some way to store data
    > Some way to fetch data from storage
    > Some way to receive requests for data
    > Some way to return responses for requested data
    @[email protected]

    View Slide

  20. THE SERVER
    > Some way to store data
    > Some way to fetch data from storage
    > Some way to receive requests for data
    > Some way to return responses for requested data
    > Some security mechanisms
    @[email protected]

    View Slide

  21. THE SERVER
    @[email protected]

    View Slide

  22. SERVER-SIDE SWIFT
    @[email protected]

    View Slide

  23. SERVER-SIDE SWIFT
    INFRASTRUCTURE TO BUILD AND RUN THE SERVER IN SWIFT
    @[email protected]

    View Slide

  24. View Slide

  25. View Slide

  26. VAPOR
    A SWIFT LIBRARY FOR RUNNING YOUR SERVER
    @[email protected]

    View Slide

  27. View Slide

  28. FLUENT
    A SWIFT ADAPTER FOR TALKING TO MULTIPLE DATA STORAGE TYPES
    @[email protected]

    View Slide

  29. THE SERVER
    > Some way to store data
    > Some way to fetch data from storage
    > Some way to receive requests for data
    > Some way to return responses for requested data
    > Some security mechanisms
    @[email protected]

    View Slide

  30. THE SERVER
    > Some way to store data (Fluent)
    > Some way to fetch data from storage (Fluent)
    > Some way to receive requests for data
    > Some way to return responses for requested data
    > Some security mechanisms
    @[email protected]

    View Slide

  31. THE SERVER
    > Some way to store data (Fluent)
    > Some way to fetch data from storage (Fluent)
    > Some way to receive requests for data (Vapor)
    > Some way to return responses for requested data
    (Vapor)
    > Some security mechanisms (mostly Vapor)
    @[email protected]

    View Slide

  32. View Slide

  33. A CLIENT
    A THING THAT INTERACTS WITH DATA FROM YOUR SERVER
    @[email protected]

    View Slide

  34. A CLIENT
    > Requests data from the server
    @[email protected]

    View Slide

  35. A CLIENT
    > Requests data from the server
    > Processes and displays data from the server
    @[email protected]

    View Slide

  36. A CLIENT
    > Requests data from the server
    > Processes and displays data from the server
    > Processes and displays user input
    @[email protected]

    View Slide

  37. A CLIENT
    > Requests data from the server
    > Processes and displays data from the server
    > Processes and displays user input
    > Sends updated data to the server
    @[email protected]

    View Slide

  38. View Slide

  39. EXAMPLES OF CLIENTS
    > Website
    @[email protected]

    View Slide

  40. EXAMPLES OF CLIENTS
    > Website
    > iOS App
    > Android App
    @[email protected]

    View Slide

  41. EXAMPLES OF CLIENTS
    > Website
    > iOS App
    > Android App
    > Another server not owned by you
    @[email protected]

    View Slide

  42. EXAMPLES OF CLIENTS
    > Website
    > iOS App
    > Android App
    > Another server not owned by you
    > An IoT Device
    @[email protected]

    View Slide

  43. Photo via https://flickr.com/photos/ockam/16307930064

    View Slide

  44. THE PUBLIC API
    @[email protected]

    View Slide

  45. THE PUBLIC API
    THE DEFINITION OF HOW THE CLIENT AND SERVER CAN TALK TO EACH OTHER
    @[email protected]

    View Slide

  46. APPLICATION PROGRAMMING
    INTERFACE
    @[email protected]

    View Slide

  47. INTERFACE
    @[email protected]

    View Slide

  48. View Slide

  49. REST
    REPRESENTATIONAL STATE TRANSFER
    @[email protected]

    View Slide

  50. api/user/1
    {
    "id": "1",
    "name": "Bart Simpson"
    "age": "10"
    "family_member_ids": [ "2", "3", "4", "5" ]
    }
    @[email protected]

    View Slide

  51. api/user/1:
    {
    "id": "1",
    "name": "Bart Simpson"
    "age": "10"
    "family_members": [
    {
    "id": "2",
    "name": "Lisa Simpson",
    "age": 8
    },
    {
    "id": 3",
    "name": "Homer Simpson",
    "age": 39
    },
    {
    "id": "4",
    "name": "Marge Simpson",
    "age": 39
    },
    {
    "id": "5",
    "name": "Maggie Simpson",
    "age": 1
    }
    ]
    }
    @[email protected]

    View Slide

  52. View Slide

  53. View Slide

  54. Photo via https://flickr.com/photos/johnath/7356295658

    View Slide

  55. VAPOR
    HAS BUILT IN REST SUPPORT
    @[email protected]

    View Slide

  56. View Slide

  57. GRAPHQL
    A TYPE OF PUBLIC API THAT ALLOWS CLIENTS TO REQUEST SPECIFIC FIELDS FROM THE SERVER
    @[email protected]

    View Slide

  58. Source: https://flickr.com/photos/acaben/2816139/

    View Slide

  59. View Slide

  60. WHAT CAN I ASK FOR?
    HOW CAN I ASK FOR IT?
    @[email protected]

    View Slide

  61. REST: THERE IS NO STANDARD WAY
    @[email protected]

    View Slide

  62. GOOD: OPENAPI (FKA SWAGGER)
    @[email protected]

    View Slide

  63. GOOD: OPENAPI (FKA SWAGGER)
    @[email protected]

    View Slide

  64. LESS GOOD...
    @[email protected]

    View Slide

  65. "Oh no, we don't have any place
    where all the HTTP endpoints are
    written down. Just grep the
    codebase of the monolith, that
    should get you what you need."
    @[email protected]

    View Slide

  66. "Oh no, we don't have any place
    where all the HTTP endpoints are
    written down. Just grep the
    codebase of the monolith, that
    should get you what you need."
    (An actual thing someone told me to do the first week of a new job)

    View Slide

  67. View Slide

  68. THE GRAPHQL SCHEMA
    @[email protected]

    View Slide

  69. THE GRAPHQL SCHEMA
    A CONTRACT OF WHAT CAN BE REQUESTED, WHAT WILL BE RETURNED, AND HOW
    @[email protected]

    View Slide

  70. IN THE SCHEMA
    > Scalars
    @[email protected]

    View Slide

  71. IN THE SCHEMA
    > Scalars
    > Custom Types
    @[email protected]

    View Slide

  72. IN THE SCHEMA
    > Scalars
    > Custom Types
    > Fields/Properties
    @[email protected]

    View Slide

  73. IN THE SCHEMA
    > Scalars
    > Custom Types
    > Fields/Properties
    > Relationships (inferred)
    @[email protected]

    View Slide

  74. A NOTE ABOUT GRAPHQL

    OPTIONALITY
    @[email protected]

    View Slide

  75. is Optional Swift Type GraphQL Type
    @[email protected]

    View Slide

  76. is Optional Swift Type GraphQL Type
    No
    @[email protected]

    View Slide

  77. is Optional Swift Type GraphQL Type
    No String
    @[email protected]

    View Slide

  78. is Optional Swift Type GraphQL Type
    No String String!
    @[email protected]

    View Slide

  79. View Slide

  80. GRAPHQL TYPES ARE
    NULLABLE BY DEFAULT
    @[email protected]

    View Slide

  81. is Optional Swift Type GraphQL Type
    No String String!
    @[email protected]

    View Slide

  82. is Optional Swift Type GraphQL Type
    No String String!
    Yes
    @[email protected]

    View Slide

  83. is Optional Swift Type GraphQL Type
    No String String!
    Yes String?
    @[email protected]

    View Slide

  84. is Optional Swift Type GraphQL Type
    No String String!
    Yes String? String
    @[email protected]

    View Slide

  85. A GRAPHQL OPERATION
    @[email protected]

    View Slide

  86. A GRAPHQL OPERATION
    A DEFINITION OF A REQUEST TO FETCH, CHANGE, OR LISTEN TO CHANGES FOR YOUR DATA
    @[email protected]

    View Slide

  87. OPERATIONS
    > Query: I'd like some info, please
    @[email protected]

    View Slide

  88. OPERATIONS
    > Query: I'd like some info, please
    > Mutation: I'd like to make a change
    @[email protected]

    View Slide

  89. OPERATIONS
    > Query: I'd like some info, please
    > Mutation: I'd like to make a change
    > Subscription: Keep me up to date on changes
    @[email protected]

    View Slide

  90. OPERATIONS
    > Query: I'd like some info, please
    > Mutation: I'd like to make a change
    > Subscription: Keep me up to date on changes
    @[email protected]

    View Slide

  91. View Slide

  92. HOW DO WE COMBINE
    VAPOR AND GRAPHQL?
    @[email protected]

    View Slide

  93. Photo: https://flickr.com/photos/daveseven/6138666188

    View Slide

  94. `HTTPS://GITHUB.COM/DESIGNATEDNERD/
    MOVEITMOVEIT
    @[email protected]

    View Slide

  95. GRAPHQLKIT
    @[email protected]

    View Slide

  96. dependencies: [
    .package(url: "https://github.com/alexsteinerde/graphql-kit.git",
    from: "2.0.0"),
    ],
    @[email protected]

    View Slide

  97. View Slide

  98. WHAT IS ALL THIS?
    @[email protected]

    View Slide

  99. Source: https://flickr.com/photos/xurble/378943497

    View Slide

  100. Source: https://flickr.com/photos/xurble/378943497

    View Slide

  101. Source: https://flickr.com/photos/xurble/378943497

    View Slide

  102. HIGHEST LEVEL

    LOWEST LEVEL
    @[email protected]

    View Slide

  103. GRAPHQLKIT
    @[email protected]

    View Slide

  104. GRAPHQLKIT
    EASY GRAPHQL INTEGRATION WITH A VAPOR SERVER
    @[email protected]

    View Slide

  105. View Slide

  106. GRAPHITI
    HELPS YOU BUILD A GRAPHQL SCHEMA IN SWIFT
    @[email protected]

    View Slide

  107. GRAPHQLSWIFT/GRAPHQL
    @[email protected]

    View Slide

  108. GRAPHQLSWIFT/GRAPHQL
    THE ABSOLUTE BASE HANDLING OF GQL IN SWIFT
    @[email protected]

    View Slide

  109. VAPOR + FLUENT
    @[email protected]

    View Slide

  110. VAPOR + FLUENT
    (YOU KNOW THESE TWO ALREADY)
    @[email protected]

    View Slide

  111. View Slide

  112. // These all get pulled automatically with GraphQL kit, but you need
    // to explicitly declare them as dependencies to use them directly.
    .package(url: "https://github.com/vapor/vapor.git",
    .upToNextMajor(from: "4.0.0")),
    .package(url: "https://github.com/vapor/fluent.git",
    .upToNextMajor(from: "4.0.0")),
    .package(url: "https://github.com/vapor/fluent-sqlite-driver.git",
    .upToNextMajor(from: "4.0.0")),
    .package(url: "https://github.com/GraphQLSwift/Graphiti",
    .upToNextMinor(from: "0.26.0")),
    @[email protected]

    View Slide

  113. .target(name: "App",
    dependencies: [
    .product(name: "Fluent", package: "fluent"),
    .product(name: "FluentSQLiteDriver", package: "fluent-sqlite-driver"),
    .product(name: "Vapor", package: "vapor"),
    .product(name: "GraphQLKit", package: "graphql-kit"),
    .product(name: "GraphiQLVapor", package: "graphiql-vapor"),
    ]),
    @[email protected]

    View Slide

  114. LET'S BUILD
    A GRAPHQL API!
    @[email protected]

    View Slide

  115. BACKING OBJECT CREATION
    @[email protected]

    View Slide

  116. BACKING OBJECT CREATION:
    THE SAME AS FOR REST
    @[email protected]

    View Slide

  117. final class User: Model, Content {
    static var schema = "users"
    @ID(key: .id)
    var id: UUID?
    @Field(key: "email")
    var email: String
    @Field(key: "name")
    var name: String
    @Field(key: "hashedPassword")
    var hashedPassword: String
    @Children(for: \.$user)
    var moves: [Move]
    // ...
    }
    @[email protected]

    View Slide

  118. final class User: Model, Content {
    static var schema = "users"
    @ID(key: .id)
    var id: UUID?
    @Field(key: "email")
    var email: String
    @Field(key: "name")
    var name: String
    @Field(key: "hashedPassword")
    var hashedPassword: String
    @Children(for: \.$user)
    var moves: [Move]
    // ...
    }
    @[email protected]

    View Slide

  119. View Slide

  120. final class User: Model, Content {
    static var schema = "users"
    @ID(key: .id)
    var id: UUID?
    @Field(key: "email")
    var email: String
    @Field(key: "name")
    var name: String
    @Field(key: "hashedPassword")
    var hashedPassword: String
    @Children(for: \.$user)
    var moves: [Move]
    // ...
    }
    @[email protected]

    View Slide

  121. final class User: Model, Content {
    static var schema = "users"
    @ID(key: .id)
    var id: UUID?
    @Field(key: "email")
    var email: String
    @Field(key: "name")
    var name: String
    @Field(key: "hashedPassword")
    var hashedPassword: String
    @Children(for: \.$user)
    var moves: [Move]
    // ...
    }
    @[email protected]

    View Slide

  122. final class User: Model, Content {
    static var schema = "users"
    @ID(key: .id)
    var id: UUID?
    @Field(key: "email")
    var email: String
    @Field(key: "name")
    var name: String
    @Field(key: "hashedPassword")
    var hashedPassword: String
    @Children(for: \.$user)
    var moves: [Move]
    // ...
    }
    @[email protected]

    View Slide

  123. final class User: Model, Content {
    static var schema = "users"
    @ID(key: .id)
    var id: UUID?
    @Field(key: "email")
    var email: String
    @Field(key: "name")
    var name: String
    @Field(key: "hashedPassword")
    var hashedPassword: String
    @Children(for: \.$user)
    var moves: [Move]
    // ...
    }
    @[email protected]

    View Slide

  124. final class User: Model, Content {
    static var schema = "users"
    enum DatabaseKeys: String {
    case email
    case name
    case hashedPassword
    }
    @ID(key: .id)
    var id: UUID?
    @Field(key: "email")
    var email: String
    @Field(key: "name")
    var name: String
    @Field(key: "hashedPassword")
    var hashedPassword: String
    @Children(for: \.$user)
    var moves: [Move]
    // ...
    }
    @[email protected]

    View Slide

  125. final class User: Model, Content {
    static var schema = "users"
    enum DatabaseKeys: String {
    case email
    case name
    case hashedPassword
    }
    @ID(key: .id)
    var id: UUID?
    @Field(key: DatabaseKeys.email.fieldKey)
    var email: String
    @Field(key: DatabaseKeys.name.fieldKey)
    var name: String
    @Field(key: DatabaseKeys.hashedPassword.fieldKey)
    var hashedPassword: String
    @Children(for: \.$user)
    var moves: [Move]
    // ...
    }
    @[email protected]

    View Slide

  126. CREATING A CONFIG
    @[email protected]

    View Slide

  127. // configures your application
    public func configure(_ app: Application) throws {
    app.databases.use(.sqlite(.memory), as: .sqlite)
    // Create the appropriate migrations
    app.migrations.add(User.Migration())
    app.migrations.add(Move.Migration())
    try app.autoMigrate().wait()
    // Register the schema and its resolver.
    // TODO
    }
    @[email protected]

    View Slide

  128. // configures your application
    public func configure(_ app: Application) throws {
    app.databases.use(.sqlite(.memory), as: .sqlite)
    // Create the appropriate migrations
    app.migrations.add(User.Migration())
    app.migrations.add(Move.Migration())
    try app.autoMigrate().wait()
    // Register the schema and its resolver.
    // TODO
    }
    @[email protected]

    View Slide

  129. // configures your application
    public func configure(_ app: Application) throws {
    app.databases.use(.sqlite(.memory), as: .sqlite)
    // Create the appropriate migrations
    app.migrations.add(User.Migration())
    app.migrations.add(Move.Migration())
    try app.autoMigrate().wait()
    // Register the schema and its resolver.
    // TODO
    }
    @[email protected]

    View Slide

  130. View Slide

  131. RESOLVER
    FUNCTION TO GET DATA AND GIVE IT TO GQL
    @[email protected]

    View Slide

  132. BARE-BONES RESOLVER
    class GraphQLResolver {
    }
    @[email protected]

    View Slide

  133. DECLARING YOUR SCHEMA
    @[email protected]

    View Slide

  134. let movingSchema = try! Schema {
    Type(User.self) {
    Field("id", at: \.id)
    Field("email", at: \.email)
    Field("name", at: \.name)
    }
    }
    @[email protected]

    View Slide

  135. let movingSchema = try! Schema {
    Type(User.self) {
    Field("id", at: \.id)
    Field("email", at: \.email)
    Field("name", at: \.name)
    }
    }
    @[email protected]

    View Slide

  136. let movingSchema = try! Schema {
    Type(User.self) {
    Field("id", at: \.id)
    Field("email", at: \.email)
    Field("name", at: \.name)
    }
    }
    @[email protected]

    View Slide

  137. View Slide

  138. // configure.swift
    // Register the schema and its resolver.
    app.register(graphQLSchema: movingSchema,
    withResolver: GraphQLResolver())
    @[email protected]

    View Slide

  139. View Slide

  140. "YOU FORGOT TO DECLARE THIS TYPE
    IN THE SCHEMA, YA DING DONG"
    @[email protected]

    View Slide

  141. let movingSchema = try! Schema {
    Scalar(UUID.self)
    Type(User.self) {
    Field("id", at: \.id)
    Field("email", at: \.email)
    Field("name", at: \.name)
    }
    }
    @[email protected]

    View Slide

  142. View Slide

  143. let movingSchema = try! Schema {
    Scalar(UUID.self)
    Type(User.self) {
    Field("id", at: \.id)
    Field("email", at: \.email)
    Field("name", at: \.name)
    }
    }
    @[email protected]

    View Slide

  144. View Slide

  145. View Slide

  146. let movingSchema = try! Schema {
    Scalar(UUID.self)
    Type(User.self) {
    Field("id", at: \.id, as: UUID.self)
    Field("email", at: \.email)
    Field("name", at: \.name)
    }
    }
    @[email protected]

    View Slide

  147. View Slide

  148. View Slide

  149. View Slide

  150. HANDLING OPERATIONS
    @[email protected]

    View Slide

  151. Mutation {
    Field("createUser", at: GraphQLResolver.createUser) {
    Argument("name", at: \.name)
    Argument("email", at: \.email)
    Argument("password", at: \.password)
    }
    }
    @[email protected]

    View Slide

  152. Mutation {
    Field("createUser", at: GraphQLResolver.createUser) {
    Argument("name", at: \.name)
    Argument("email", at: \.email)
    Argument("password", at: \.password)
    }
    }
    @[email protected]

    View Slide

  153. Mutation {
    Field("createUser", at: GraphQLResolver.createUser) {
    Argument("name", at: \.name)
    Argument("email", at: \.email)
    Argument("password", at: \.password)
    }
    }
    @[email protected]

    View Slide

  154. struct UserCreateArguments: Codable {
    let name: String
    let email: String
    let password: String
    }
    extension GraphQLResolver {
    func createUser(request: Request,
    arguments: UserCreateArguments) throws -> EventLoopFuture {
    let user = try User(email: arguments.email,
    password: arguments.password,
    name: arguments.name)
    return user.create(on: request.db).map { user }
    }
    }
    @[email protected]

    View Slide

  155. struct UserCreateArguments: Codable {
    let name: String
    let email: String
    let password: String
    }
    extension GraphQLResolver {
    func createUser(request: Request,
    arguments: UserCreateArguments) throws -> EventLoopFuture {
    let user = try User(email: arguments.email,
    password: arguments.password,
    name: arguments.name)
    return user.create(on: request.db).map { user }
    }
    }
    @[email protected]

    View Slide

  156. struct UserCreateArguments: Codable {
    let name: String
    let email: String
    let password: String
    }
    extension GraphQLResolver {
    func createUser(request: Request,
    arguments: UserCreateArguments) throws -> EventLoopFuture {
    let user = try User(email: arguments.email,
    password: arguments.password,
    name: arguments.name)
    return user.create(on: request.db).map { user }
    }
    }
    @[email protected]

    View Slide

  157. Mutation {
    Field("createUser", at: GraphQLResolver.createUser) {
    Argument("name", at: \.name)
    Argument("email", at: \.email)
    Argument("password", at: \.password)
    }
    }
    @[email protected]

    View Slide

  158. Mutation {
    Field("createUser", at: GraphQLResolver.createUser) {
    Argument("name", at: \.name)
    Argument("email", at: \.email)
    Argument("password", at: \.password)
    }
    }
    @[email protected]

    View Slide

  159. TRYING IT OUT
    @[email protected]

    View Slide

  160. EASIEST: GRAPHIQL
    @[email protected]

    View Slide

  161. // Something to help visualize and test your schema and operations
    .package(url: "https://github.com/alexsteinerde/graphiql-vapor.git",
    .upToNextMajor(from: "2.0.0")),
    @[email protected]

    View Slide

  162. .target(name: "App",
    dependencies: [
    // (existing dependencies)
    .product(name: "GraphiQLVapor", package: "graphiql-vapor"),
    ]),
    @[email protected]

    View Slide

  163. import GraphiQLVapor
    public func configure(_ app: Application) throws {
    //... (stuff you already saw)
    // Enable GraphiQL web page for built-in goodness
    if !app.environment.isRelease {
    app.enableGraphiQL()
    }
    }
    @[email protected]

    View Slide

  164. View Slide

  165. View Slide

  166. View Slide

  167. View Slide

  168. View Slide

  169. APOLLO STUDIO
    HTTPS://STUDIO.APOLLOGRAPHQL.COM/SANDBOX/EXPLORER
    @[email protected]

    View Slide

  170. View Slide

  171. View Slide

  172. View Slide

  173. View Slide

  174. View Slide

  175. CORS: CROSS-ORIGIN
    RESOURCE SHARING
    @[email protected]

    View Slide

  176. CORS: CROSS-ORIGIN
    RESOURCE SHARING
    (DISABLED BY DEFAULT IN VAPOR)
    @[email protected]

    View Slide

  177. let corsConfiguration = CORSMiddleware.Configuration(
    allowedOrigin: .custom("https://studio.apollographql.com"),
    allowedMethods: [.POST],
    allowedHeaders: [
    .accept,
    .contentType,
    .accessControlAllowOrigin,
    .origin
    ]
    )
    let cors = CORSMiddleware(configuration: corsConfiguration)
    app.middleware.use(cors)
    @[email protected]

    View Slide

  178. !
    PITFALLS
    @[email protected]

    View Slide

  179. LOTS OF FIGHTING
    WITH THE COMPILER
    @[email protected]

    View Slide

  180. View Slide

  181. THERE CAN ONLY BE ONE
    (RESOLVER CLASS)
    @[email protected]

    View Slide

  182. class MoveResolver {
    private let userResolver = UserResolver()
    // MARK: - Pass-Through Methods
    func createUser(request: Request,
    arguments: UserCreateArguments) throws
    -> EventLoopFuture {
    try userResolver.createUser(request: request,
    arguments: arguments)
    }
    //...
    }
    @[email protected]

    View Slide

  183. GraphQLResolver+Moves.swift
    class GraphQLResolver {
    func createMove(request: Request,
    arguments: CreateMoveArguments) -> EventLoopFuture {
    //...
    }
    }
    GraphQLResolver+Users.swift
    extension GraphQLResolver
    func createUser(request: Request,
    arguments: UserCreateArguments) throws -> EventLoopFuture {
    //...
    }
    }
    @[email protected]

    View Slide

  184. DATA LOADING INEFFICIENCIES
    @[email protected]

    View Slide

  185. DATA LOADER
    @[email protected]

    View Slide

  186. 404 DATA LOADER NOT FOUND
    @[email protected]

    View Slide

  187. View Slide

  188. THE VERDICT:
    @[email protected]

    View Slide

  189. THE VERDICT:
    IT DEPENDS
    @[email protected]

    View Slide

  190. View Slide

  191. THE VERDICTS:
    >
    !
    If you know a lot about both Vapor/Fluent AND GQL
    @[email protected]

    View Slide

  192. THE VERDICTS
    >
    !
    If you know a lot about both Vapor/Fluent AND GQL
    >
    "
    If you know a lot about Vapor/Fluent but not GQL
    @[email protected]

    View Slide

  193. THE VERDICTS
    >
    !
    If you know a lot about both Vapor/Fluent AND GQL
    >
    "
    If you know a lot about Vapor/Fluent but not GQL
    >
    #
    If you know a lot about GQL but not Vapor/Fluent
    @[email protected]

    View Slide

  194. THE VERDICTS
    >
    !
    If you know a lot about both Vapor/Fluent AND GQL
    >
    "
    If you know a lot about Vapor/Fluent but not GQL
    >
    #
    If you know a lot about GQL but not Vapor/Fluent
    >
    $
    If you don't know a lot about either one
    @[email protected]

    View Slide

  195. View Slide

  196. OBLIGATORY SUMMARY SLIDE
    @[email protected]

    View Slide

  197. OBLIGATORY SUMMARY SLIDE
    >
    !
    If you know a lot about both Vapor/Fluent AND GQL
    >
    "
    If you know a lot about Vapor/Fluent but not GQL
    >
    #
    If you know a lot about GQL but not Vapor/Fluent
    >
    $
    If you don't know a lot about either one
    @[email protected]

    View Slide

  198. THANK YOU!
    @[email protected]

    View Slide

  199. LINKS!
    > Vapor/Fluent Docs: https://docs.vapor.codes/
    > GraphQLKit: https://github.com/alexsteinerde/
    graphql-kit/
    > (slightly outdated) Tutorial on getting started:
    https://www.kodeco.com/21148796-graphql-
    tutorial-for-server-side-swift-with-vapor-
    getting-started
    @[email protected]

    View Slide