Page updated Jan 16, 2024

Read application data

You can read application data using the Amplify GraphQL 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.

The Amplify CLI automatically creates list and get queries for any @model type in your GraphQL API. 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.

The Amplify CLI automatically generates GraphQL client code for all possible GraphQL operations—mutations, queries, and subscriptions. For JavaScript applications, this generated code is saved in the src/graphql folder by default.

To run a GraphQL query, import the generated query and run it with API.graphql:

import { API } from 'aws-amplify'; import * as queries from './graphql/queries'; import { GraphQLQuery } from '@aws-amplify/api'; import { ListTodosQuery, GetTodoQuery } from './API'; // Simple query const allTodos = await API.graphql<GraphQLQuery<ListTodosQuery>>({ query: queries.listTodos }); console.log(allTodos); // result: { "data": { "listTodos": { "items": [/* ..... */] } } } // Fetch a single record by its identifier const oneTodo = await API.graphql<GraphQLQuery<GetTodoQuery>>({ query: queries.getTodo, variables: { id: 'some id' } });
1import { API } from 'aws-amplify';
2import * as queries from './graphql/queries';
3import { GraphQLQuery } from '@aws-amplify/api';
4import { ListTodosQuery, GetTodoQuery } from './API';
5
6// Simple query
7const allTodos = await API.graphql<GraphQLQuery<ListTodosQuery>>({
8 query: queries.listTodos
9});
10console.log(allTodos); // result: { "data": { "listTodos": { "items": [/* ..... */] } } }
11
12// Fetch a single record by its identifier
13const oneTodo = await API.graphql<GraphQLQuery<GetTodoQuery>>({
14 query: queries.getTodo,
15 variables: { id: 'some id' }
16});
import { API } from 'aws-amplify'; import * as queries from './graphql/queries'; // Simple query const allTodos = await API.graphql({ query: queries.listTodos }); console.log(allTodos); // result: { "data": { "listTodos": { "items": [/* ..... */] } } } //Fetch a single record by its identifier const oneTodo = await API.graphql({ query: queries.getTodo, variables: { id: 'some id' } });
1import { API } from 'aws-amplify';
2import * as queries from './graphql/queries';
3
4// Simple query
5const allTodos = await API.graphql({ query: queries.listTodos });
6console.log(allTodos); // result: { "data": { "listTodos": { "items": [/* ..... */] } } }
7
8//Fetch a single record by its identifier
9const oneTodo = await API.graphql({
10 query: queries.getTodo,
11 variables: { id: 'some id' }
12});
Troubleshooting
Troubleshoot unauthorized errors

You can run into unauthorized errors if you don't have at least one auth rule defined.

Use the @auth directive to configure authorization rules for public, signed-in user, per-user, and per-user-group data access. Authorization rules operate on the deny-by-default principle, meaning that if an authorization rule is not specifically configured, it is denied.

type Todo @model @auth(rules: [{ allow: owner }]) { content: String }
1type Todo @model @auth(rules: [{ allow: owner }]) {
2 content: String
3}

In the previous example, each signed-in user, or "owner", of a Todo can create, read, update, and delete their own Todos.

Amplify also allows you to restrict the allowed operations, combine multiple authorization rules, and apply fine-grained field-level authorization.

type Todo @model @auth(rules: [{ allow: public, operations: [read] }, { allow: owner }]) { content: String }
1type Todo
2 @model
3 @auth(rules: [{ allow: public, operations: [read] }, { allow: owner }]) {
4 content: String
5}

In the previous example, everyone (public) can read every Todo, but the owner (authenticated users) can create, read, update, and delete their own Todos.

To learn more, see Authorization rules for the GraphQL API.

Learn more
Custom authorization mode to query data

Each AWS AppSync API uses a default authorization mode when you configure your app. To override this default, pass an authMode property. For example, this is useful when you have public reads through API Key auth and authenticated reads through IAM auth. The following examples show how you can query data with the custom authorization mode:

import { API } from 'aws-amplify'; import { GraphQLQuery, GRAPHQL_AUTH_MODE } from '@aws-amplify/api'; import * as queries from './graphql/queries'; import { GetTodoQuery } from './API'; const todos = await API.graphql<GraphQLQuery<GetTodoQuery>>({ query: queries.listTodos, authMode: GRAPHQL_AUTH_MODE.AWS_IAM });
1import { API } from 'aws-amplify';
2import { GraphQLQuery, GRAPHQL_AUTH_MODE } from '@aws-amplify/api';
3import * as queries from './graphql/queries';
4import { GetTodoQuery } from './API';
5
6const todos = await API.graphql<GraphQLQuery<GetTodoQuery>>({
7 query: queries.listTodos,
8 authMode: GRAPHQL_AUTH_MODE.AWS_IAM
9});
import { API } from 'aws-amplify'; import { GRAPHQL_AUTH_MODE } from '@aws-amplify/api'; import * as queries from './graphql/queries'; const todos = await API.graphql({ query: queries.listTodos, authMode: GRAPHQL_AUTH_MODE.AWS_IAM });
1import { API } from 'aws-amplify';
2import { GRAPHQL_AUTH_MODE } from '@aws-amplify/api';
3import * as queries from './graphql/queries';
4
5const todos = await API.graphql({
6 query: queries.listTodos,
7 authMode: GRAPHQL_AUTH_MODE.AWS_IAM
8});

Filter list queries

As your data grows, you will need to paginate your list queries. Fortunately, this is already built in to the Amplify-generated GraphQL API.

import { API } from 'aws-amplify'; import { listTodos } from './graphql/queries'; import { ListTodosQueryVariables, ListTodosQuery } from './API'; const variables: ListTodosQueryVariables = { filter: { priority: { eq: 1 } } }; await API.graphql<GraphQLQuery<ListTodosQuery>>({ query: listTodos, variables: variables });
1import { API } from 'aws-amplify';
2import { listTodos } from './graphql/queries';
3import { ListTodosQueryVariables, ListTodosQuery } from './API';
4
5const variables: ListTodosQueryVariables = {
6 filter: {
7 priority: {
8 eq: 1
9 }
10 }
11};
12
13await API.graphql<GraphQLQuery<ListTodosQuery>>({
14 query: listTodos,
15 variables: variables
16});
import { API } from 'aws-amplify'; import { listTodos } from './graphql/queries'; const variables = { filter: { priority: { eq: 1 } } }; await API.graphql({ query: listTodos, variables: variables });
1import { API } from 'aws-amplify';
2import { listTodos } from './graphql/queries';
3
4const variables = {
5 filter: {
6 priority: {
7 eq: 1
8 }
9 }
10};
11
12await API.graphql({
13 query: listTodos,
14 variables: variables
15});
Learn more
How do filters work?

You can find the input schemas in the Docs pane of the GraphQL explorer or inside your auto-generated /graphql folder. They look like this:

listTodos( filter: ModelTodoFilterInput limit: Int nextToken: String): ModelTodoConnection input ModelTodoFilterInput { id: ModelIDInput priority: ModelIntInput # ... all your other Todo fields here and: [ModelTodoFilterInput] or: [ModelTodoFilterInput] not: ModelTodoFilterInput }
1listTodos(
2 filter: ModelTodoFilterInput
3 limit: Int
4 nextToken: String): ModelTodoConnection
5
6input ModelTodoFilterInput {
7 id: ModelIDInput
8 priority: ModelIntInput
9 # ... all your other Todo fields here
10 and: [ModelTodoFilterInput]
11 or: [ModelTodoFilterInput]
12 not: ModelTodoFilterInput
13}

The input types in your schema indicate what kinds of filtering you can perform on them. For example, an integer field like ModelIntInput has this schema:

input ModelIntInput { ne: Int # "not equal to" eq: Int # "equal to" le: Int # "less than or equal to" lt: Int # "less than" ge: Int # "greater than or equal to" gt: Int # "greater than" between: [Int] attributeExists: Boolean attributeType: ModelAttributeTypes }
1input ModelIntInput {
2 ne: Int # "not equal to"
3 eq: Int # "equal to"
4 le: Int # "less than or equal to"
5 lt: Int # "less than"
6 ge: Int # "greater than or equal to"
7 gt: Int # "greater than"
8 between: [Int]
9 attributeExists: Boolean
10 attributeType: ModelAttributeTypes
11}

These filters vary based on the type of the field, but are linked to corresponding Amazon DynamoDB queries.

Compound filters

You can combine filters with and, or, and not Boolean logic. Observe, in the previous auto-generated schema, that ModelTodoFilterInput 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:

import { API } from 'aws-amplify'; import { listTodos } from './graphql/queries'; import { ListTodosQueryVariables, ListTodosQuery } from './API'; const variables: ListTodosQueryVariables = { filter: { or: [{ priority: { eq: 1 } }, { priority: { eq: 2 } }] } }; await API.graphql<GraphQLQuery<ListTodosQuery>>({ query: listTodos, variables: variables });
1import { API } from 'aws-amplify';
2import { listTodos } from './graphql/queries';
3import { ListTodosQueryVariables, ListTodosQuery } from './API';
4
5const variables: ListTodosQueryVariables = {
6 filter: {
7 or: [{ priority: { eq: 1 } }, { priority: { eq: 2 } }]
8 }
9};
10
11await API.graphql<GraphQLQuery<ListTodosQuery>>({
12 query: listTodos,
13 variables: variables
14});
import { API } from 'aws-amplify'; import { listTodos } from './graphql/queries'; const variables = { filter: { or: [{ priority: { eq: 1 } }, { priority: { eq: 2 } }] } }; await API.graphql({ query: listTodos, variables: variables });
1import { API } from 'aws-amplify';
2import { listTodos } from './graphql/queries';
3
4const variables = {
5 filter: {
6 or: [{ priority: { eq: 1 } }, { priority: { eq: 2 } }]
7 }
8};
9
10await API.graphql({
11 query: listTodos,
12 variables: variables
13});

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.

import { API } from 'aws-amplify'; import { listTodos } from './graphql/queries'; import { ListTodosQueryVariables, ListTodosQuery } from './API'; // Fetch first 20 records const variables: ListTodosQueryVariables = { limit: 20 // add filter as needed }; const res = (await API.graphql) < GraphQLQuery<ListTodosQuery>({ query: listTodos, variables: variables }); const { items: itemsPage1, nextToken } = res.data?.listTodos; // Fetch the next 20 records variables.nextToken = nextToken; const res = (await API.graphql) < GraphQLQuery<ListTodosQuery>({ query: listTodos, variables: variables }); const { items: itemsPage2 } = res.data?.listTodos;
1import { API } from 'aws-amplify';
2import { listTodos } from './graphql/queries';
3import { ListTodosQueryVariables, ListTodosQuery } from './API';
4
5// Fetch first 20 records
6const variables: ListTodosQueryVariables = {
7 limit: 20
8 // add filter as needed
9};
10
11const res =
12 (await API.graphql) <
13 GraphQLQuery<ListTodosQuery>({
14 query: listTodos,
15 variables: variables
16 });
17
18const { items: itemsPage1, nextToken } = res.data?.listTodos;
19
20// Fetch the next 20 records
21variables.nextToken = nextToken;
22
23const res =
24 (await API.graphql) <
25 GraphQLQuery<ListTodosQuery>({
26 query: listTodos,
27 variables: variables
28 });
29
30const { items: itemsPage2 } = res.data?.listTodos;
import { API } from 'aws-amplify'; import { listTodos } from './graphql/queries'; // Fetch first 20 records const variables = { limit: 20 // add filter as needed }; const res = await API.graphql({ query: listTodos, variables: variables }); const { items: itemsPage1, nextToken } = res.data.listTodos; // Fetch the next 20 records variables.nextToken = nextToken; const res = await API.graphql({ query: listTodos, variables: variables }); const { items: itemsPage2 } = res.data.listTodos;
1import { API } from 'aws-amplify';
2import { listTodos } from './graphql/queries';
3
4// Fetch first 20 records
5const variables = {
6 limit: 20
7 // add filter as needed
8};
9
10const res = await API.graphql({
11 query: listTodos,
12 variables: variables
13});
14
15const { items: itemsPage1, nextToken } = res.data.listTodos;
16
17// Fetch the next 20 records
18variables.nextToken = nextToken;
19
20const res = await API.graphql({
21 query: listTodos,
22 variables: variables
23});
24
25const { items: itemsPage2 } = res.data.listTodos;
Walkthrough
Implement pagination in your GraphQL API

This brief walkthrough provides additional step-by-step guidance for implementing pagination.

When working with a large record set, you may want to only fetch the first N number of items. For example, let's start with a basic GraphQL schema for a Todo app:

type Todo @model { id: ID! title: String! description: String }
1type Todo @model {
2 id: ID!
3 title: String!
4 description: String
5}

When the API is created with an @model directive, the following queries will automatically be created for you:

type Query { getTodo(id: ID!): Todo listTodos( filter: ModelTodoFilterInput limit: Int nextToken: String ): ModelTodoConnection }
1type Query {
2 getTodo(id: ID!): Todo
3 listTodos(
4 filter: ModelTodoFilterInput
5 limit: Int
6 nextToken: String
7 ): ModelTodoConnection
8}

Next, take a look at the ModelTodoConnection type to get an idea of the data that will be returned when the listTodos query is run:

type ModelTodoConnection { items: [Todo] nextToken: String }
1type ModelTodoConnection {
2 items: [Todo]
3 nextToken: String
4}

When querying the API using the listTodos query, the return type will be of ModelTodoConnection, meaning you can return both an array of Todos and a nextToken.

The nextToken handles pagination. If the nextToken is null, that means there is no more data to return from the API. If the nextToken is present, you can use the value as an argument to the next listTodos query to return the next selection set from the API.

To test this, try creating five Todos using a mutation like this:

mutation createTodo { createTodo(input: { title: "Todo 1" description: "My first todo" }) { id title description } }
1mutation createTodo {
2 createTodo(input: {
3 title: "Todo 1"
4 description: "My first todo"
5 }) {
6 id
7 title
8 description
9 }
10}

Next, you can set the limit of the number of Todos in a query by specifying a limit argument. In this query, you will set the limit to two items and request a nextToken as a return value:

query listTodos { listTodos(limit: 2) { items { id title description } nextToken } }
1query listTodos {
2 listTodos(limit: 2) {
3 items {
4 id
5 title
6 description
7 }
8 nextToken
9 }
10}

Now, to query the next two items from the API, specify this nextToken as the argument:

query listTodos { listTodos(limit: 2, nextToken: <your_token>) { items { id title description } nextToken } }
1query listTodos {
2 listTodos(limit: 2, nextToken: <your_token>) {
3 items {
4 id
5 title
6 description
7 }
8 nextToken
9 }
10}

When there are no other items left to be returned, the nextToken in the response will be set to null.

Querying from a JavaScript application

The listTodos query is auto-generated by the CLI; for reference, it should look something like this:

const listTodos = ` query listTodos($limit: Int) { listTodos(limit: $limit) { items { id title description } nextToken } } `;
1const listTodos = `
2 query listTodos($limit: Int) {
3 listTodos(limit: $limit) {
4 items {
5 id
6 title
7 description
8 }
9 nextToken
10 }
11 }
12`;

To specify a limit in a query from a JavaScript application, you can use the following code by setting the limit as a variable:

import { API } from 'aws-amplify'; async function fetchTodos() { const todoData = await API.graphql({ query: listTodos, variables: { limit: 2 } }); console.log({ todoData }); }
1import { API } from 'aws-amplify';
2
3async function fetchTodos() {
4 const todoData = await API.graphql({
5 query: listTodos,
6 variables: {
7 limit: 2
8 }
9 });
10 console.log({ todoData });
11}

The data returned from the API request should look like this (with the items array containing however many items have been created):

{ "data" { "listTodos" { "items": [{ id: "001", title: "Todo 1", description: "My first todo" }], "nextToken": "<token-id>" } } }
1{
2 "data" {
3 "listTodos" {
4 "items": [{ id: "001", title: "Todo 1", description: "My first todo" }],
5 "nextToken": "<token-id>"
6 }
7 }
8}

To set the nextToken in a query from a JavaScript application, you can use the following code:

import { API } from 'aws-amplify'; async function fetchTodos() { const todoData = await API.graphql({ query: listTodos, variables: { limit: 2, nextToken: '<token-id>' } }); console.log({ todoData }); }
1import { API } from 'aws-amplify';
2
3async function fetchTodos() {
4 const todoData = await API.graphql({
5 query: listTodos,
6 variables: {
7 limit: 2,
8 nextToken: '<token-id>'
9 }
10 });
11 console.log({ todoData });
12}

Limitations:

Cancel read requests

You may cancel any query or mutation request made through the API category by keeping a reference to the promise returned.

const promise = API.graphql({ query: "..." }); try { await promise; } catch (error) { console.log(error); // If the error is because the request was cancelled you can confirm here. if (API.isCancel(error)) { console.log(error.message); // "my message for cancellation" // handle user cancellation logic } } ... // To cancel the above request API.cancel(promise, "my message for cancellation");
1const promise = API.graphql({ query: "..." });
2
3try {
4 await promise;
5} catch (error) {
6 console.log(error);
7 // If the error is because the request was cancelled you can confirm here.
8 if (API.isCancel(error)) {
9 console.log(error.message); // "my message for cancellation"
10 // handle user cancellation logic
11 }
12}
13
14...
15
16// To cancel the above request
17API.cancel(promise, "my message for cancellation");

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

async function makeAPICall() { return API.graphql({ query: '...' }); } const promise = makeAPICall(); // The following will NOT cancel the request. API.cancel(promise, 'my error message');
1async function makeAPICall() {
2 return API.graphql({ query: '...' });
3}
4const promise = makeAPICall();
5
6// The following will NOT cancel the request.
7API.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: