Overview
We're familiar with queries, but when we want to actually change, insert, or delete data, we need to reach for a new tool: GraphQL mutations.
In this lesson, we will:
- Implement a mutation operation to login with an email
- Enable our app's login view and execute the mutation
Building a mutation
A mutation is an operation we use to change data on the server. In our application, the login mutation will create a session based on our email address.
Note: The way you log in to this particular server might differ from the way you log in with your own server. Login is often handled by middleware, or a layer totally separate from GraphQL, like OAuth.
Also note that a typical authentication flow should require a password—but for this tutorial, anyone is allowed to book flights with a valid email address!
Let's take a look at the mutation fields that exist in our app's schema.
Open up Sandbox, or expand the embedded Sandbox below.
Let's return to the Schema tab. Just below the Query tab, we'll find Mutation. Let's click on this type and scroll down to take a look at the login
mutation field:
Click the play button on the right to open that mutation in the Explorer tab. When it opens, click the plus sign next to login
to add the operation:
Adding this field prefills the Operation panel with the following syntax.
mutation Login {login {}}
Notice the red error indicator that shows up in Explorer? This is because the type returned by the mutation is User
, which is is an object type: this means we need to choose at least one of the User
type's fields for the mutation to return. For our purposes, we only need the token
field, so add it by clicking the plus sign next to it.
You'll also notice that email
wasn't automatically added as an argument even though it doesn't seem to have a default value: that's because email
is of type String
—without an exclamation point (!
)—which remember, in GraphQL, means that it's not required (although obviously you won't get too far without it).
Click the plus sign next to the email
argument to add that argument.
We'll also notice that Explorer has added a variable to your Variables section to match the login email.
Click the Submit Operation button to run the mutation. Because we sent null
for the email address, we'll get back null
for the login:
Now, replace null
in the Variables section with an actual email address:
{"email": "me@example.com"}
Submit the operation, and this time you'll get an actual response:
Next, copy the operation, either manually or using the three-dot menu's Copy operation option.
Add the mutation to the project
Now that the mutation is working, we'll add it to our project. Create an empty file named Login.graphql
(don't add it to the app target) next to your other GraphQL files and paste the contents of the mutation:
mutation Login($email: String!) {login(email: $email) {token}}
Note: We've also marked the email
variable as non-nullable by adding !
to the end of the type, since we always want to pass a value for it.
Run code generation in terminal to generate the code for the mutation.
./apollo-ios-cli generate
Implement the login logic
Next, let's update our app to use this mutation. Open up LoginViewModel.swift
. We'll add a few new imports:
import Swiftimport Apolloimport RocketReserverAPIimport KeychainSwift
Next, scroll down to the TODO
inside the login(with email: String?)
method. Clear out the comment and replace it with the following code.
Network.shared.apollo.perform(mutation: LoginMutation(email: email)) { [weak self] result indefer {self?.isSubmitEnabled = true}switch result {case .success(let graphQLResult):if let token = graphQLResult.data?.login?.token {// TODO - store token securelyself?.isPresented = false}if let errors = graphQLResult.errors {self?.appAlert = .errors(errors: errors)}case .failure(let error):self?.appAlert = .errors(errors: [error])}}
Next, we need to store the login credential that's returned by the server. Login credentials should always be stored in the Keychain, but interacting with it directly is challenging, so we'll be using the KeychainSwift
library which has already been added as a Swift Package to this project.
Now we'll use the KeychainSwift
we imported earlier. Replace the TODO - store token securely
after unwrapping the token with the following:
let keychain = KeychainSwift()keychain.set(token, forKey: LoginView.loginKeychainKey)
Display the login view
Next we need to check if the user is logged in when booking or cancelling a trip. We'll use this state to determine whether or not we show the LoginView
.
To do this, go to the DetailViewModel.swift
. Scroll down until you locate the bookOrCancel
method. We'll notice that this method already contains some code to call isLoggedIn()
; if that returns false
, it sets the flag to show the login view.
func bookOrCancel() {guard self.isLoggedIn() else {isShowingLogin = truereturn}// TODO}
Currently this method always returns false, so let's update that now.
First add the following import
to DetailViewModel.swift
:
import SwiftUIimport RocketReserverAPI+ import KeychainSwift
Next, replace the contents of the isLoggedIn()
method (all the way at the bottom of the class) with the following:
private func isLoggedIn() -> Bool {let keychain = KeychainSwift()return keychain.get(LoginView.loginKeychainKey) != nil}
Let's put it all together, and try out our mutation!
Test the login mutation
Build and run the application, select a launch from the list to get to the DetailView
. You should see that clicking the Book now! button in the UI shows the login view.
If you login with this email, me@example.com
, subsequent presses of Book now! no longer show the login view.
Up next
We've implemented our mechanism to "log in", but there's more work to do: in the next lesson, we'll learn how to authenticate operations with our login token.
Share your questions and comments about this lesson
This course is currently in
You'll need a GitHub account to post below. Don't have one? Post in our Odyssey forum instead.