$30 off During Our Annual Pro Sale. View Details »

Server Side Swift and GraphQL: A Match Made In ...

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

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
  2. THE SERVER > Some way to store data > Some

    way to fetch data from storage > Some way to receive requests for data @[email protected]
  3. 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]
  4. 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]
  5. 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]
  6. 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]
  7. 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]
  8. A CLIENT > Requests data from the server > Processes

    and displays data from the server > Processes and displays user input @[email protected]
  9. 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]
  10. EXAMPLES OF CLIENTS > Website > iOS App > Android

    App > Another server not owned by you > An IoT Device @[email protected]
  11. 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]
  12. "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]
  13. "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)
  14. 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]
  15. 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]
  16. // 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]
  17. .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]
  18. 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]
  19. 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]
  20. 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]
  21. 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]
  22. 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]
  23. 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]
  24. 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]
  25. 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]
  26. // 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]
  27. // 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]
  28. // 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]
  29. let movingSchema = try! Schema<GraphQLResolver, Request> { Type(User.self) { Field("id",

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

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

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

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

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

    Field("id", at: \.id, as: UUID.self) Field("email", at: \.email) Field("name", at: \.name) } } @[email protected]
  35. struct UserCreateArguments: Codable { let name: String let email: String

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

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

    let password: String } extension GraphQLResolver { func createUser(request: Request, arguments: UserCreateArguments) throws -> EventLoopFuture<User> { let user = try User(email: arguments.email, password: arguments.password, name: arguments.name) return user.create(on: request.db).map { user } } } @[email protected]
  38. // 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]
  39. 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]
  40. 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]
  41. class MoveResolver { private let userResolver = UserResolver() // MARK:

    - Pass-Through Methods func createUser(request: Request, arguments: UserCreateArguments) throws -> EventLoopFuture<User> { try userResolver.createUser(request: request, arguments: arguments) } //... } @[email protected]
  42. GraphQLResolver+Moves.swift class GraphQLResolver { func createMove(request: Request, arguments: CreateMoveArguments) ->

    EventLoopFuture<Move> { //... } } GraphQLResolver+Users.swift extension GraphQLResolver func createUser(request: Request, arguments: UserCreateArguments) throws -> EventLoopFuture<User> { //... } } @[email protected]
  43. 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]
  44. 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]
  45. 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]
  46. 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]
  47. 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]