Skip to main content
Build an iOS Application

Module 4: Add a GraphQL API and Database

In this module, you will use the Amplify CLI and libraries to configure and add a GraphQL API to your app

Introduction

Overview

Now that you have created and configured the app with user authentication, you will add an API and create, read, update, delete (CRUD) operations on a database.

In this module, you will add an API to our app using the Amplify CLI and libraries. The API you will be creating is a GraphQL API that uses AWS AppSync (a managed GraphQL service) which is backed by Amazon DynamoDB (a NoSQL database). For an introduction to GraphQL, visit this page.

The app you will be building is a note-taking app where users can create, delete, and list notes. This example gives you a good idea of how to build many popular types of CRUD+L (create, read, update, delete, and list) applications.

What you will accomplish

In this tutorial, you will:

  • Create and deploy a GraphQL API

  • Write frontend code to interact with the API

Key concepts

API – Provides a programming interface that allows communication and interactions between multiple software intermediaries.

GraphQL – A query language and server-side API implementation based on a typed representation of your application. This API representation is declared using a schema based on the GraphQL type system. To learn more about GraphQL, visit this page.

Implementation

Time to complete

20 minutes

Services used

Create a GraphQL API service and a database

1. Add the Amplify API

Open the Terminal, navigate to your project root directory , and run the following command:

amplify add api

2. Configure the API

When prompted, make the following selections:

Select from one of the below mentioned services: 
    ❯ GraphQL  
Authorization modes:
    Choose the default authorization type for the API
        ❯ Amazon Cognito User Pool        
Configure additional auth types?
    N

3. Confirm selections

Validate the selected options, and choose  Continue.

Here is the GraphQL API that we will create. Select a setting to edit or continue (Use arrow keys)
  Name: gettingstarted
  Authorization modes: Amazon Cognito User Pool (default)
  Conflict detection (required for DataStore): Disabled
❯ Continue

4. Edit the schema

Select Blank Schema and choose Y when asked to edit the schema:

Choose a schema template:
    ❯ Blank Schema 
Do you want to edit the schema now?
    Y

5. Update the schema

As we want to represent the model we previously defined in the  Note.swift  file, use the following schema and save the file:

type Note
@model
@auth (rules: [ { allow: owner } ]) {
id: ID!
name: String!
description: String
image: String
}

The data model is made of one class named  Note  and four String properties:  id  and  name  are mandatory;  description  and  image  are optional.

  • The  @model  transformer indicates we want to create a database to store these data.

  • The  @auth  transformer adds authentication rules to allow access to these data. For this project, we want only the owner of Notes to have access to them.

Delete the  Note.swift  file, we will re-generate the models in the next step.

Generate client-side code

Amplify generates client-side code.  

1. Generate the code

To generate the code, run the following command in your terminal:

amplify codegen models

This creates Swift files in the amplify/generated/models directory and automatically add them to your project.

Deploy the API service and database

1. Deploy the backend database

To deploy the backend API and database we have just created, in your terminal run the following command:

amplify push

2. Push the deployment

When prompted, make the following selections:

amplify push
Are you sure you want to continue?
    Y   
Do you want to generate code for your newly created GraphQL API?
    N

Add API client library to the project

1. Open the general tab

Navigate to the  General  tab of your Target application (Your Project > Targets > General), and select the plus (+) in the  Frameworks, Libraries, and Embedded Content  section. 

Screenshot of the Xcode project 'GettingStarted' showing the General tab for configuring frameworks, libraries, embedded content, and app settings as part of an AWS Amplify iOS app build tutorial.

2. Choose the plugin

Choose the AWSAPIPlugin , and select  Add

Screenshot showing the selection of AWS Amplify plugins, including AWSAPIPlugin, in the process of building an iOS app. The image demonstrates the frameworks and libraries list within the Amplify Package manager used in an AWS Amplify tutorial for iOS development.

3. Verify the dependency created

You have now added AWSAPIPlugin as a dependency for your project.

Screenshot showing a table that lists frameworks, libraries, and embedded content for an iOS app using Amplify, AWSAPIPlugin, and AWSCognitoAuthPlugin, as part of an Amplify tutorial module with API integration.

Configure the Amplify API library at runtime

1. Configure the library

Navigate back to Xcode , and open the  GettingStartedApp.swift  file.

To configure Amplify API, you will need to:

  • Add the  import AWSAPIPlugin  statement.

  • Create the  AWSAPIPlugin  plugin and register it with Amplify.

Your code should look like the following:

GettingStartedApp.swift file code

Add this code to your file

javascript
import Amplify
import AWSAPIPlugin
import AWSCognitoAuthPlugin
import SwiftUI

@main
struct GettingStartedApp: App {
    init() {
        do {
            try Amplify.add(plugin: AWSCognitoAuthPlugin())
            try Amplify.add(plugin: AWSAPIPlugin(modelRegistration: AmplifyModels()))
            try Amplify.configure()
            print("Initialized Amplify");
        } catch {
            print("Could not initialize Amplify: \(error)")
        }
    }

    var body: some Scene {
        WindowGroup {
            LandingView()
                .environmentObject(AuthenticationService())
        }
    }
}

Create a class to support API CRUD operations

1. Create a NotesService.swift file

Create a new Swift file named NotesService.swift with the following code.

This class allows to fetch all notes, save a new note, and delete an existing note, while also publishing the fetched notes in a notes array.

NotesService.swift file code

Add this code to your file

javascript
import Amplify
import SwiftUI

@MainActor
class NotesService: ObservableObject {
    @Published var notes: [Note] = []

    func fetchNotes() async {
        do {
            let result = try await Amplify.API.query(request: .list(Note.self))
            switch result {
            case .success(let notesList):
                print("Fetched \(notesList.count) notes")
                notes = notesList.elements
            case .failure(let error):
                print("Fetch Notes failed with error: \(error)")
            }
        } catch {
            print("Fetch Notes failed with error: \(error)")
        }
    }

    func save(_ note: Note) async {
        do {
            let result = try await Amplify.API.mutate(request: .create(note))
            switch result {
            case .success(let note):
                print("Save note completed")
                notes.append(note)
            case .failure(let error):
                print("Save Note failed with error: \(error)")
            }
        } catch {
            print("Save Note failed with error: \(error)")
        }
    }

    func delete(_ note: Note) async {
        do {
            let result = try await Amplify.API.mutate(request: .delete(note))
            switch result {
            case .success(let note):
                print("Delete note completed")
                notes.removeAll(where: { $0.id == note.id })
            case .failure(let error):
                print("Delete Note failed with error: \(error)")
            }
        } catch {
            print("Delete Note failed with error: \(error)")
        }
    }
}

Update the existing UI

1. List notes

Make the following changes to the  NotesView.swift  file:

  • Add a new  @EnvironmentObject private var notesService: NotesService   property

  • Delete the local  notes  array and instead use published  notesService.notes   when creating the List items in the ForEach loop.

  • Call  notesService.fetchNotes()   when the view appears. We can do this using the  task(priority:_:) method.

Your file should look like the following code.

NotesView.swift file code

Modify your code with this code

javascript
struct NotesView: View {
    @EnvironmentObject private var authenticationService: AuthenticationService
    @EnvironmentObject private var notesService: NotesService

    var body: some View {
        NavigationStack{
            List {
                if notesService.notes.isEmpty {
                    Text("No notes")
                }
                ForEach(notesService.notes, id: \.id) { note in
                    NoteView(note: note)
                }
            }
            .navigationTitle("Notes")
            .toolbar {
                Button("Sign Out") {
                    Task {
                        await authenticationService.signOut()
                    }
                }
            }
        }
        .task {
            await notesService.fetchNotes()
        }
    }
}

2. Set the notesService object

Since the  notesService  variable is marked with a  @EnvironmentObject   property wrapper annotation, we need to set it using the  environmentObject(_:) view modifier on an ancestor view.

Update the GettingStartedApp.swift body to set the NotesService object:

notesService.swift file code

Modify your code with this code

javascript
    var body: some Scene {
        WindowGroup {
            LandingView()
                .environmentObject(NotesService())
                .environmentObject(AuthenticationService())
        }
    }

3. Create notes

Create a new Swift file named SaveNoteView.swift with the following content.

This view displays text fields for a name, description, and image. It also has a  Save Note  button that when tapped, it creates a  Note  object and calls  noteService.save(_:)  to save it.

Note:  The view automatically dismisses itself by the usage of the  @Environment(\.dismiss)  property.

SaveNoteView.swift file code

Add this code to your file

javascript
import SwiftUI

struct SaveNoteView: View {
    @Environment(\.dismiss) private var dismiss
    @EnvironmentObject private var notesService: NotesService
    @State private var name = ""
    @State private var description = ""
    @State private var image = ""

    var body: some View {
        Form {
            Section("Details") {
                TextField("Name", text: $name)
                TextField("Description", text: $description)
            }
            
            Section("Picture") {
                TextField("Image Name", text: $image)
            }
            
            Button("Save Note") {
                let note = Note(
                    name: name,
                    description: description.isEmpty ? nil : description,
                    image: image.isEmpty ? nil : image
                )
                
                Task {
                    await notesService.save(note)
                    dismiss()
                }
            }
        }
    }
}

4. Update the NotesView.swift file

Update the  NotesView.swift   file with the following information:

  • Add a new   @State private var isSavingNote = false property

  • Add a new  ToolbarItem  at the bottom, with a  "⨁ New Note"  button that sets  isSavingNote = true

  • Show  SaveNoteView()  inside a  sheet(isPresented:)  method, using the  isSavingNote property.

Your file should now look like the following:

Updated SaveNoteView.swift file code

Update your code with this code

javascript
struct NotesView: View {
    @EnvironmentObject private var authenticationService: AuthenticationService
    @EnvironmentObject private var notesService: NotesService
    @State private var isSavingNote = false

    var body: some View {
        NavigationStack{
            List {
                if notesService.notes.isEmpty {
                    Text("No notes")
                }
                ForEach(notesService.notes, id: \.id) { note in
                    NoteView(note: note)
                }
            }
            .navigationTitle("Notes")
            .toolbar {
                Button("Sign Out") {
                    Task {
                        await authenticationService.signOut()
                    }
                }
            }
            .toolbar {
                ToolbarItem(placement: .bottomBar) {
                    Button("⨁ New Note") {
                        isSavingNote = true
                    }
                    .bold()
                }
            }
            .sheet(isPresented: $isSavingNote) {
                SaveNoteView()
            }
        }
        .task {
            await notesService.fetchNotes()
        }
    }
}

5. Delete notes

To implement this we can rely on the onDelete(perform:) method of ForEach:

Delete functionality code

Add this code to your code

javascript
    ForEach(notesService.notes, id: \.id) { note in
        NoteView(note: note)
    }
    .onDelete { indices in
        for index in indices {
            let note = notesService.notes[index]
            Task {
                await notesService.delete(note)
            }
        }
    }

6. Verify your code is correct

Your NotesView.swift file should look like the following:

Updated NotesView.swift file code

Your code should look like this.

javascript
import SwiftUI

struct NotesView: View {
    @EnvironmentObject private var authenticationService: AuthenticationService
    @EnvironmentObject private var notesService: NotesService
    @State private var isSavingNote = false

    var body: some View {
        NavigationStack{
            List {
                if notesService.notes.isEmpty {
                    Text("No notes")
                }
                ForEach(notesService.notes, id: \.id) { note in
                    NoteView(note: note)
                }
                .onDelete { indices in
                    for index in indices {
                        let note = notesService.notes[index]
                        Task {
                            await notesService.delete(note)
                        }
                    }
                }
            }
            .navigationTitle("Notes")
            .toolbar {
                Button("Sign Out") {
                    Task {
                        await authenticationService.signOut()
                    }
                }
            }
            .toolbar {
                ToolbarItem(placement: .bottomBar) {
                    Button("⨁ New Note") {
                        isSavingNote = true
                    }
                    .bold()
                }
            }
            .sheet(isPresented: $isSavingNote) {
                SaveNoteView()
            }
        }
        .task {
            await notesService.fetchNotes()
        }
    }
}

Build and test

1. Run the project

To verify everything works as expected, build, and run the project.

Choose the   button in the toolbar. Alternatively, you can also do it by navigating to  Product -> Run , or by pressing  Cmd + R .

The iOS simulator will open and the app should show you the Notes view, assuming you are still signed in.

2. Create a new note

Choose the " ⨁ New Note " button at the bottom to create a new list.

Screenshot of an iPhone 15 Pro simulator displaying the Notes screen for Module 5 of the Build iOS App with Amplify tutorial, showing no notes and options to sign out or create a new note.

3. Enter details

Enter details for the note and choose Save Note.

Screenshot of an iOS app tutorial showing the 'Create Note' screen using AWS Amplify. The image displays an input form with fields for note details and picture, along with a 'Save Note' button, running in an iPhone simulator.

4. View note

View the note in the list.

Screenshot of an iPhone 15 Pro simulator showing a simple notes app. The app displays a section titled 'Notes' with a single note entry labeled 'New Note' and the description 'This is an example description.' UI elements include a 'Sign Out' link and a button to add a new note.

5. Delete the note

You can delete a note by swiping from the left of its row.

Screenshot of an iOS app tutorial showing the user interface for deleting a note using AWS Amplify. The screen displays a note with an example description and a red 'Delete' button, within an iPhone 15 Pro simulator.

Conclusion

You have now added a GraphQL API and configured create, read, and delete functionality in your app. In the next module, we will add UI and behavior to manage pictures.

Add the ability to store images