Pagination
Many queries in Healthie’s API are paginated. We use offset-based pagination.
When a query takes in an offset argument, that specifies where the data returned should start.
For example, our entries query has a page size of 10. if you have a list of 20 entries, and want to load all of them in, you would first do an entries query with offset of 0 and then an entries query with an offset of 10. Many queries have a corresponding “count” query (e.g entries has an entriesCount), That “count” query lets you see the total amount of data that you will need to page through.
If you send in an offset value that is too large (e.g 150 for a data set that only contains 100 objects), our API will just return an empty array.
Cursor Pagination
Section titled “Cursor Pagination”Healthie suggests using the new Cursor pagination instead of the previously available offset-based pagination. It is more stable, performant, and easier to use. Each query supporting Cursor pagination has an after input argument and returned objects provide a cursor field. To fetch the next page of results, use the cursor value from the last item returned in the current query.
Consider the following query:
{ announcements(page_size: 2, should_paginate: true) { id cursor } announcementsCount}Which should give a result like this:
{ "data": { "announcements": [ { "id": "3", "cursor": "eyJrIjpbIjMiXX0=" }, { "id": "2", "cursor": "eyJrIjpbIjIiXX0=" } ], "announcementsCount": 3 }}Now, let’s take the cursor value of the last item on the list and use it in the next query:
{ announcements(page_size: 1, should_paginate: true, after: "eyJrIjpbIjIiXX0=") { id cursor } announcementsCount}It should return the next set of result after the specified cursor.
{ "data": { "announcements": [ { "id": "1", "cursor": "eyJrIjpbIjEiXX0=" } ], "announcementsCount": 3 }}Queries that currently support Cursor pagination:
announcementsapi_keysappointment_typesappointmentsaudit_logbilling_itemscard_issuescare_planscms1500scommentsconversation_membershipscourse_membershipscoursescpt_codescustomEmailscustom_module_formsdocumentsdocument_viewingsentriesfood_searchform_answer_groupsgoalsgoals_datagoal_historiesgoal_instancesicd_codesinsurance_planslab_orderslocationsmeal_searchnotesnotificationsofferingsoffering_couponsonboarding_flowspermission_templatesproductsreceived_faxesreferring_physiciansrequested_paymentssent_faxessent_webhookssmart_phrasessuper_billstasksusersuser_groupsuser_package_selectionswebhooks
Connection-based Cursor Pagination
Section titled “Connection-based Cursor Pagination”Healthie is introducing a new pagination model based on the Relay Connection specification. This model provides a more robust and feature-rich way to paginate through collections of data and is being used by tech leaders like GitHub and Shopify.
Key Differences from Previous Pagination
Section titled “Key Differences from Previous Pagination”Previously, our API supported a hybrid approach to cursor pagination where:
- Queries accepted an
afterparameter for basic cursor-based navigation - Each model had a
cursorfield - Separate count queries (e.g.,
usersCount) were required for total counts - Limited metadata about the result set was available
The new Connection-based pagination provides several improvements:
- Standardized way to navigate through result sets
- Built-in metadata about the connection (e.g., total count)
- Information about whether more results are available
- Clear separation between connection metadata and node data
Using Connection-based Pagination
Section titled “Using Connection-based Pagination”Basic Query Structure
Section titled “Basic Query Structure”{ users(first: 10) { edges { node { id name email } cursor } page_info { has_next_page has_previous_page start_cursor end_cursor } total_count }}Here’s an example of how to query a collection using the new pagination model:
Connection Fields
Section titled “Connection Fields”Each connection provides the following fields:
edges: A list of edge objects, each containing:node: The actual object (e.g., User, Appointment, Document)cursor: An opaque string used for pagination; the value represents the cursor for the given edge
page_info: Metadata about the current page:has_next_page: Boolean indicating if more results exist after this pagehas_previous_page: Boolean indicating if more results exist before this pagestart_cursor: Opaque cursor for the first edge in the current pageend_cursor: Opaque cursor for the last edge in the current page
total_count: The total number of items in the collection
Pagination Arguments
Section titled “Pagination Arguments”Connections support both forward and backward pagination:
Forward pagination:
first: Number of items to returnafter: Cursor to start after (use theend_cursorfrom previous query)
Backward pagination:
last: Number of items to returnbefore: Cursor to end before (use thestart_cursorfrom previous query)
Example: Forward Pagination
Section titled “Example: Forward Pagination”Initial query:
{ appointments(first: 10) { edges { node { id start contact_type appointment_type { id } attendees { id email } } cursor } page_info { has_next_page end_cursor } }}Next page:
{ appointments(first: 10, after: "previously_returned_end_cursor") { edges { node { id start contact_type appointment_type { id } attendees { id email } } cursor } page_info { has_next_page end_cursor } }}Example: Backward Pagination
Section titled “Example: Backward Pagination”Previous page:
{ appointments(last: 10, before: "previously_returned_start_cursor") { edges { node { id start contact_type appointment_type { id } attendees { id email } } cursor } page_info { has_previous_page start_cursor } }}Best Practices
Section titled “Best Practices”-
Avoid Mixed Pagination: While the API supports both
first/afterandlast/beforearguments, using them together is discouraged as it can lead to confusing results. -
Cursor Handling:
- For forward pagination, use the
end_cursorfrompage_infoas theafterparameter - For backward pagination, use the
start_cursorfrompage_infoas thebeforeparameter - Treat cursors as opaque strings; do not attempt to decode or modify them
- For forward pagination, use the
-
Page Size:
- Always specify either
firstorlast - Keep page sizes reasonable (recommended: 10-50 items)
- Be aware that requesting very large page sizes may impact performance or result in query complexity limits
- Always specify either
Migration Guide
Section titled “Migration Guide”If you’re currently using our hybrid cursor pagination, here’s how to migrate to the new Connection-based pagination:
- Remove separate count queries (e.g.,
usersCount) and usetotal_countfrom the connection (users { total_count }) - Update your pagination logic to use the
page_infoobject for navigation - Update your data processing to handle the edges/node structure
Before migration:
{ users(after: "some_cursor") { id name cursor } usersCount}After migration:
{ users(first: 10, after: "some_cursor") { edges { node { id name cursor } cursor } page_info { has_next_page end_cursor } total_count }}References
Section titled “References”For deeper understanding of cursor-based pagination and the Connection specification:
- GraphQL Cursor Connections Specification - The official Relay Connection specification that this implementation follows
- GraphQL Pagination Best Practices - GraphQL’s official documentation on pagination approaches
- Explaining GraphQL Connections - Apollo’s detailed guide on implementing and using GraphQL connections