Amplify has re-imagined the way frontend developers build fullstack applications. Develop and deploy without the hassle.

Page updated May 21, 2024

Set up Amplify Data

In this guide, you will learn how to set up Amplify Data. This includes building a real-time API and database using TypeScript to define your data model, and securing your API with authorization rules. We will also explore using AWS Lambda to scale to custom use cases.

Before you begin, you will need:

  • Node.js v18.16.0 or later
  • npm v6.14.4 or later
  • git v2.14.1 or later

With Amplify Data, you can build a secure, real-time API backed by a database in minutes. After you define your data model using TypeScript, Amplify will deploy a real-time API for you. This API is powered by AWS AppSync and connected to an Amazon DynamoDB database. You can secure your API with authorization rules and scale to custom use cases with AWS Lambda.

Building your data backend

If you've run npm create amplify@latest already, you should see an amplify/data/resource.ts file, which is the central location to configure your data backend. The most important element is the schema object, which defines your backend data models (a.model()) and custom queries (a.query()), mutations (a.mutation()), and subscriptions (a.subscription()).

amplify/data/resource.ts
import { a, defineData, type ClientSchema } from '@aws-amplify/backend';
const schema = a.schema({
Todo: a.model({
content: a.string(),
isDone: a.boolean()
})
.authorization(allow => [allow.publicApiKey()])
});
// Used for code completion / highlighting when making requests from frontend
export type Schema = ClientSchema<typeof schema>;
// defines the data resource to be deployed
export const data = defineData({
schema,
authorizationModes: {
defaultAuthorizationMode: 'apiKey',
apiKeyAuthorizationMode: { expiresInDays: 30 }
}
});

Every a.model() automatically creates the following resources in the cloud:

  • a DynamoDB database table to store records
  • query and mutation APIs to create, read (list/get), update, and delete records
  • createdAt and updatedAt fields that help you keep track of when each record was initially created or when it was last updated
  • real-time APIs to subscribe for create, update, and delete events of records

The allow.publicApiKey() rule designates that anyone authenticated using an API key can create, read, update, and delete todos.

To deploy these resources to your cloud sandbox, run the following CLI command in your terminal:

Terminal
npx ampx sandbox --outputs-out-dir <path_to_swift_project>

Connect your application code to the data backend

Once the cloud sandbox is up and running, it will also create an amplify_outputs.json file, which includes the relevant connection information to your data backend, like your API endpoint URL and API key.

To connect your frontend code to your backend, you need to:

  1. Configure the Amplify library with the Amplify client configuration file (amplify_outputs.json)
  2. Generate a new API client from the Amplify library
  3. Make an API request with end-to-end type-safety

Drag and drop the amplify_outputs.json file from the Finder into Xcode.

Next, add Amplify Library for Swift through Swift Package Manager. In Xcode, select File > Add Packages....

Then, enter the Amplify Library for Swift GitHub repo URL (https://github.com/aws-amplify/amplify-swift) into the search bar and hit Enter.

Once the result is loaded, choose Up to Next Major Version as the Dependency Rule, then click Add Package.

Choose which of the libraries you want added to your project. For this tutorial, select AWSAPIPlugin and Amplify, then click Add Package.

Now let's add the necessary plugins into the Swift application by customizing the init() function of your app:

MyAmplifyApp
import SwiftUI
import Amplify
import AWSAPIPlugin
@main
struct MyAmplifyApp: App {
init() {
let awsApiPlugin = AWSAPIPlugin(modelRegistration: AmplifyModels())
do {
try Amplify.add(plugin: awsApiPlugin)
try Amplify.configure(with: .amplifyOutputs)
print("Initialized Amplify");
} catch {
// simplified error handling for the tutorial
print("Could not initialize Amplify: \(error)")
}
}
var body: some Scene {
WindowGroup {
ContentView()
}
}
}

Finally, let's generate the GraphQL client code for your Swift application. Amplify Data uses GraphQL under the hood to make query, mutation, and subscription requests. The generated GraphQL client code helps you to author fully-typed API requests without needing to hand-author GraphQL requests and manually map them to Swift code.

Terminal
npx ampx generate graphql-client-code --format modelgen --model-target swift --out <path_to_swift_project>/AmplifyModels

Drag and drop the AmplifyModels folder into your Xcode project to add the generated files.

Write data to your backend

Create a new file called TodoViewModel.swift and the createTodo function with the following code:

TodoViewModel.swift
import Foundation
import Amplify
@MainActor
class TodoViewModel: ObservableObject {
func createTodo() {
let todo = Todo(
content: "Build iOS Application",
isDone: false
)
Task {
do {
let result = try await Amplify.API.mutate(request: .create(todo))
switch result {
case .success(let todo):
print("Successfully created todo: \(todo)")
case .failure(let error):
print("Got failed result with \(error.errorDescription)")
}
} catch let error as APIError {
print("Failed to create todo: ", error)
} catch {
print("Unexpected error: \(error)")
}
}
}
}

Update ContentView.swift with the following code:

ContentView.swift
struct ContentView: View {
// Create an observable object instance.
@StateObject var vm = TodoViewModel()
var body: some View {
VStack {
Button(action: {
vm.createTodo()
}) {
HStack {
Text("Add a New Todo")
Image(systemName: "plus")
}
}
.accessibilityLabel("New Todo")
}
}
}

Now if you run the application, and click on the "Add a New Todo" button, you should see a log indicating a todo was created:

Logs
Successfully created todo: Todo(id: XYZ ...)

Read data from your backend

Next, list all your todos and then refetch the todos after a todo has been added:

Update the listTodos function in the TodoViewModel.swift for listing to-do items:

TodoViewModel.swift
@MainActor
class TodoViewModel: ObservableObject {
@Published var todos: [Todo] = []
func createTodo() {
/// ...
}
func listTodos() {
Task {
do {
let result = try await Amplify.API.query(request: .list(Todo.self))
switch result {
case .success(let todos):
print("Successfully retrieved list of todos: \(todos)")
self.todos = todos.elements
case .failure(let error):
print("Got failed result with \(error.errorDescription)")
}
} catch let error as APIError {
print("Failed to query list of todos: ", error)
} catch {
print("Unexpected error: \(error)")
}
}
}
}

Now let's update the UI code to observe the todos.

ContentView.swift
import SwiftUI
import Amplify
struct ContentView: View {
@StateObject var vm = TodoViewModel()
var body: some View {
VStack {
List(vm.todos, id: \.id) { todo in
Text(todo.content ?? "")
}
// .. Add a new Todo button
}
.task {
await vm.listTodos()
}
}
}

Subscribe to real-time updates

To add real-time updates, you can use the subscription feature of Amplify Data. It allows to subscribe to onCreate, onUpdate, and onDelete events of the application. In our example, let's append the list every time a new todo is added.

First, add a private variable to store the subscription. Then create the subscription on the init() initializer, and add the subscribe() and cancel() functions.

TodoViewModel.swift
@MainActor
class TodoViewModel: ObservableObject {
@Published var todos: [Todo] = []
private var subscription: AmplifyAsyncThrowingSequence<GraphQLSubscriptionEvent<Todo>>
init() {
self.subscription = Amplify.API.subscribe(request: .subscription(of: Todo.self, type: .onCreate))
}
func subscribe() {
Task {
do {
for try await subscriptionEvent in subscription {
handleSubscriptionEvent(subscriptionEvent)
}
} catch {
print("Subscription has terminated with \(error)")
}
}
}
private func handleSubscriptionEvent(_ subscriptionEvent: GraphQLSubscriptionEvent<Todo>) {
switch subscriptionEvent {
case .connection(let subscriptionConnectionState):
print("Subscription connect state is \(subscriptionConnectionState)")
case .data(let result):
switch result {
case .success(let createdTodo):
print("Successfully got todo from subscription: \(createdTodo)")
todos.append(createdTodo)
case .failure(let error):
print("Got failed result with \(error.errorDescription)")
}
}
}
func cancel() {
self.subscription.cancel()
}
func createTodo() {
/// ...
}
func listTodos() {
/// ...
}
}

Then in ContentView.swift, when the view appears, call vm.subscribe(). On disappear, cancel the subscription.

ContentView.swift
struct ContentView: View {
@StateObject var vm = TodoViewModel()
var body: some View {
VStack {
// ...
}
.onDisappear {
vm.cancel()
}
.task {
vm.listTodos()
vm.subscribe()
}
}
}

Now if you rerun your app, a new todo should be appended to the list every time you create a new todo.

Conclusion

Success! You've learned how to create your first real-time API and database with Amplify Data.

Next steps

There's so much more to discover with Amplify Data. Learn more about: