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:
- An application connected to the API
- Data already created to view
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.
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 client.graphql
:
import { generateClient } from 'aws-amplify/api';import * as queries from './graphql/queries';
const client = generateClient();
// Simple queryconst allTodos = await client.graphql({ query: queries.listTodos });console.log(allTodos); // result: { "data": { "listTodos": { "items": [/* ..... */] } } }
// Fetch a single record by its identifierconst oneTodo = await client.graphql({ query: queries.getTodo, variables: { id: 'some id' }});
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 { generateClient } from 'aws-amplify/api';import { listTodos } from './graphql/queries';
const client = generateClient();
const variables = { filter: { priority: { eq: 1 } }};
await client.graphql({ query: listTodos, variables: variables});
Learn moreHow 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}
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}
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 { generateClient } from 'aws-amplify/api';import { listTodos } from './graphql/queries';
const client = generateClient();
const variables = { filter: { or: [{ priority: { eq: 1 } }, { priority: { eq: 2 } }] }};
await client.graphql({ query: listTodos, variables: variables});
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 { generateClient } from 'aws-amplify/api';import { listTodos } from './graphql/queries';
const client = generateClient();
const res = await client.graphql({ query: listTodos, variables: { // Fetch first 20 records limit: 20 }});
const { items: itemsPage1, nextToken } = res.data.listTodos;
// Fetch the next 20 recordsvariables.nextToken = nextToken;
const res = await client.graphql({ query: listTodos, variables: { limit: 20 }});
const { items: itemsPage2 } = res.data.listTodos;
WalkthroughImplement 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}
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}
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}
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 }}
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 }}
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 }}
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 } }`;
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 { generateClient } from 'aws-amplify/api';
const client = generateClient();
async function fetchTodos() { const todoData = await client.graphql({ query: listTodos, variables: { limit: 2 } }); console.log({ todoData });}
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>" } }}
To set the nextToken
in a query from a JavaScript application, you can use the following code:
import { generateClient } from 'aws-amplify/api';
const client = generateClient();
async function fetchTodos() { const todoData = await client.graphql({ query: listTodos, variables: { limit: 2, nextToken: '<token-id>' } }); console.log({ todoData });}
Cancel read requests
You may cancel any query or mutation request made through the API category by keeping a reference to the promise returned.
import { generateClient } from 'aws-amplify/api'
const client = generateClient()
const promise = client.graphql({ query: "..." });
try { await promise;} catch (error) { console.log(error); // If the error is because the request was cancelled you can confirm here. if (client.isCancelError(error)) { console.log(error.message); // "my message for cancellation" // handle user cancellation logic }}
...
// To cancel the above requestclient.cancel(promise, "my message for cancellation");
You need to ensure that the promise returned from client.graphql()
has not been modified. Typically, async functions wrap the promise being returned into another promise. For example, the following will not work:
import { generateClient } from 'aws-amplify/api';
const client = generateClient();
async function makeAPICall() { return client.graphql({ query: '...' });}const promise = makeAPICall();
// The following will NOT cancel the request.client.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: