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

Page updated May 8, 2024

Read application data

You can read application data using the Amplify Data client. In this guide, we will review the difference between reading data and getting data, how to filter query results to get just the data you need, and how to paginate results to make your data more manageable. We will also show you how to cancel these requests when needed.

Before you begin, you will need:

List and get your data

Queries are used to read data through the API and include the list and get operations. Amplify Data automatically creates list and get queries for any a.model() type in your schema. The list query retrieves multiple items, such as Todo items, without needing to specific an identifier for a particular record. This is best suited for getting an overview or summary of items, or for enhancing the list operation to filter the items by specific criteria. When you want to query a single entry by an identifier, you would use get to retrieve a specific Todo item.

Note: The cost structure of your underlying data source can impact the cost to run some queries. For example, the list operation uses Amazon DynamoDB "scan operations," which can use more read request units than the get operation. You will want to review the associated costs for these operations for your data source. In our example, we are using DynamoDB. You can learn more about how DynamoDB costs are calculated by visiting Amazon DynamoDB pricing.

You can list items by first generating the Data client with your backend Data schema. Then you can list items of your desired model:

1import { generateClient } from 'aws-amplify/data';
2import { type Schema } from '@/amplify/data/resource';
3
4const client = generateClient<Schema>();
5
6// list all items
7const { data: todos, errors } = await client.models.Todo.list();
8
9// get a specific item
10const { data: todo, errors } = await client.models.Todo.get({
11 id: '...',
12});
Troubleshooting
Troubleshoot unauthorized errors

Each API request uses an authorization mode. If you get unauthorized errors, you may need to update your authorization mode. To override the default authorization mode defined in your amplify/data/resource.ts file, pass an authMode property to the request or the client. The following examples show how you can mutate data with a custom authorization mode:

1import { generateClient } from 'aws-amplify/data';
2import { type Schema } from '@/amplify/data/resource';
3
4const client = generateClient<Schema>();
5
6const { errors, data: todos } = await client.models.Todo.list({
7 authMode: 'apiKey',
8});

Filter list queries

As your data grows, you will need to paginate your list queries. Fortunately, this is already built in to Amplify Data.

1import { generateClient } from 'aws-amplify/data';
2import { type Schema } from '@/amplify/data/resource';
3
4const client = generateClient<Schema>();
5
6const { data: todos, errors } = await client.models.Todo.list({
7 filter: {
8 content: {
9 beginsWith: 'hello'
10 }
11 }
12});

Compound filters

You can combine filters with and, or, and not Boolean logic. Observe that filter is recursive in respect to those fields. So if, for example, you wanted to filter for priority values of 1 or 2, you would do this:

1import { generateClient } from 'aws-amplify/data';
2import { type Schema } from '@/amplify/data/resource';
3
4const client = generateClient<Schema>();
5
6const { data: todos, errors } = await client.models.Todo.list({
7 filter: {
8 or: [
9 {
10 priority: { eq: '1' }
11 },
12 {
13 priority: { eq: '2' }
14 }
15 ]
16 }
17});

Note that querying for priority of 1 and 2 would return no results, because this is Boolean logic instead of natural language.

Paginate list queries

To paginate your list query results, make a subsequent list query request with the nextToken and limit input variable set. The limit variable limits how many results are returned. The response will include a nextToken you can use to request the next page of data. A nextToken is a very long string that represents the cursor to the starting item of the next query made with these filters.

1import { generateClient } from 'aws-amplify/data';
2import { type Schema } from '@/amplify/data/resource';
3
4const client = generateClient<Schema>();
5
6const {
7 data: todos,
8 nextToken, // Repeat this API call with the nextToken until the returned nextToken is `null`
9 errors
10} = await client.models.Todo.list({
11 limit: 100, // default value is 100
12 nextToken: 'eyJ2ZXJzaW9uejE1a2...' // previous nextToken
13});

If you're building a React application, you can use the usePagination hook in Amplify UI to help with managing the pagination user experience.

1import * as React from 'react';
2import { Pagination } from '@aws-amplify/ui-react';
3
4export const PaginationHasMorePagesExample = () => {
5 const [pageTokens, setPageTokens] = React.useState([null]);
6 const [currentPageIndex, setCurrentPageIndex] = React.useState(1);
7 const [hasMorePages, setHasMorePages] = React.useState(true);
8
9 const handleNextPage = async () => {
10 if (hasMorePages && currentPageIndex === pageTokens.length) {
11 const { data: todos, nextToken } = await client.models.Todo.list({
12 nextToken: pageTokens[pageTokens.length - 1]
13 });
14
15 if (!nextToken) {
16 setHasMorePages(false);
17 }
18
19 setPageTokens([...pageTokens, nextToken]);
20 }
21
22 setCurrentPageIndex(currentPageIndex + 1);
23 };
24
25 return (
26 <Pagination
27 currentPage={currentPageIndex}
28 totalPages={pageTokens.length}
29 hasMorePages={hasMorePages}
30 onNext={handleNextPage}
31 onPrevious={() => setCurrentPageIndex(currentPageIndex - 1)}
32 onChange={(pageIndex) => setCurrentPageIndex(pageIndex)}
33 />
34 );
35};

Limitations:

Fetch only the data you need with custom selection set

A business domain model may contain many models with numerous fields. However, apps typically only need subsets of the data or fields to meet the requirements of different components or screens. It is necessary to have a mechanism to retrieve subsets of models and their relationships. This mechanism would help optimize data usage for screens and components by only transferring needed data. Having this capability would improve the app's data efficiency, latency, and the end user's perceived performance.

A custom selection set allows consumers to specify, on a per-call basis, the fields the consumer wants to retrieve; this is possible for all operations that return data (CRUDL + observeQuery). The desired fields are specified in a strongly typed way (discoverable through IntelliSense) with a "dot notation".

1// same way for all CRUDL: .create, .get, .update, .delete, .list, .observeQuery
2const { data: blogWithSubsetOfData, errors } = await client.models.Blog.get(
3 { id: blog.id },
4 {
5 selectionSet: ['author.email', 'posts.*'],
6 }
7);

TypeScript type helpers for Amplify Data

When using TypeScript, you frequently need to specify data model types for type generics. For instance, with React's useState, you provide a type in TypeScript to ensure type-safety in your component code using the state. Use the Schema["MODEL_NAME"]["type"] pattern to get TypeScript types for the shapes of data models returned from the backend API. This allows you to get consumable TypeScript types for the shapes of the data model return values coming from the backend API.

1import { type Schema } from '@/amplify/data/resource';
2
3type Post = Schema['Post']['type'];
4
5const [posts, setPosts] = useState<Post[]>([]);

You can combine the Schema["MODEL_NAME"]["type"] type with the SelectionSet helper type to describe the return type of API requests using the selectionSet parameter:

1import type { SelectionSet } from 'aws-amplify/data';
2import type { Schema } from '../amplify/data/resource';
3
4
5const selectionSet = ['content', 'blog.author.*', 'comments.*'] as const;
6type PostWithComments = SelectionSet<Schema['Post']['type'], typeof selectionSet>;
7
8// ...
9const [posts, setPosts] = useState<PostWithComments[]>([]);
10
11const fetchPosts = async () => {
12 const { data: postsWithComments } = await client.models.Post.list({
13 selectionSet,
14 });
15 setPosts(postsWithComments);
16}

Cancel read requests

You can cancel any query API request by calling .cancel on the query request promise that's returned by .list(...) or .get(...).

1const promise = client.models.Todo.list();
2// ^ Note: we're not awaiting the request, we're returning the promise
3
4try {
5 await promise;
6} catch (error) {
7 console.log(error);
8 // If the error is because the request was cancelled you can confirm here.
9 if (client.isCancelError(error)) {
10 console.log(error.message); // "my message for cancellation"
11 // handle user cancellation logic
12 }
13}
14...
15
16// To cancel the above request
17client.cancel(promise, "my message for cancellation");

You need to ensure that the promise returned from .list() or .get() has not been modified. Typically, async functions wrap the promise being returned into another promise. For example, the following will not work:

1async function makeAPICall() {
2 return client.models.Todo.list();
3}
4const promise = makeAPICall();
5
6// The following will NOT cancel the request.
7client.cancel(promise, 'my error message');

Conclusion

Congratulations! You have finished the Read application data guide. In this guide, you learned how to read your data through get and list queries.

Next steps

Our recommended next steps include subscribing to real-time events to look for mutations in your data and continuing to build out and customize your information architecture for your data. Some resources that will help with this work include: