Skip to content

GraphQL Flow

emmastephenson edited this page Dec 13, 2021 · 11 revisions

GraphQL in SimpleReport

Our application uses GraphQL to make queries and mutations across the application. It controls the flow of data between the frontend, backend, and database for almost all logged-in users (i.e., it doesn't handle anonymous requests; we have separate Controller classes for those.)

GraphQL has extensive documentation which you may find helpful: https://graphql.org/learn/

This page aims to document SimpleReport's specific GraphQL implementation for engineers new to the project.

How it's wired

GraphQL has both queries and mutations. Queries just return data from the database, while mutations let you edit the data in our database. A query example would be fetching the test queue for a given facility, while a mutation would be something like adding a new patient.

Our frontend calls queries and mutations which are sent to various Resolver classes on the backend. These resolvers connect with our database, either directly or indirectly, to fetch or manipulate data as needed. The mutation/query definitions are shared in a contract between the frontend and backend, defined in .graphqls files. The frontend must call these operations exactly as specified in the GraphQL file, and the backend must define a method to implement them.

Where to begin

We define our GraphQL mutations and types in main.graphqls, admin.graphqls, and wiring.graphqls.

main.graphqls defines most of the interactions seen by end users - adding patients, facilities, tests, api users, etc, as well as fetching data like tests, organization name, and the patient list. It also defines several types that are used across different queries and mutations, such as Organizations and Facilities. These types are usually closely related to our database tables, but that's an artificial construct - there's nothing that directly ties the two.

admin.graphqls defines superadmin or support-capable queries and mutations. These include mutations like resending a group of results to ReportStream, creating or identity verifying a new organization, and marking api users or facilities as deleted or undeleted.

wiring.graphqls defines some common types for GraphQL usage, including permission levels, roles, and some statuses. It does not currently define any mutations or queries.

Whenever these .graphqls files are changed, you need to run yarn codegen on the frontend. There's an additional generated file, graphql.tsx that converts all our queries, mutations, and types to Typescript-compatible types. Before the team used this generated file, to use a mutation or query on the frontend you'd need to first re-define the mutation/query as a Typescript type within your frontend file. It was very annoying, so just use the generated types instead!

An end to end example

Let's follow the path of a GraphQL query all the way through our application. (Note that this was written 12/1/2021, apologies if it's out of date by the time you're reading this.)

In this example, we'll look at fetching the list of users on the Manage Users page.

Manage Users Page

Frontend

ManageUsersContainer.tsx and ManageUsers.tsx hold most of the frontend logic for this page. The query we're interested in here is getUsersAndStatus, which fetches basic user and account status information for the user list. It returns the user's id, name, email address, and account status (as string) for each user in an organization.

The query itself is defined in main.graphqls, but for our purposes on the frontend, it's contained in the generated Typescript file graphql.tsx.

Notice that the query called in ManageUsersContainer has a refetch parameter, which gets passed to ManageUsers. This refetch allows us to call the query again in the event that the data underlying the query changes, i.e. a user has been added to or removed from the organization.

The initial state within ManageUsers also contains a list of users, which is the output of the initial call to getUsersAndStatus. This list is sorted and passed into the UsersSideNav component.

Backend

The getUsersWithStatus query is defined in UserResolver.java. This in turn calls the ApiUserService method getUsersAndStatusInCurrentOrg.

Unlike most other GraphQL methods, this query does not only look at the database for data. It also queries Okta to retrieve the user's status. The Okta calls are directed to LiveOktaRepository, which fetches the list of all users for the given organization and returns a map of their email to account status. Then, the database is directly queried with the ApiUserRepository, returning all database objects for a given set of login emails.

The results of these two queries are merged into an ApiUserWithStatus object, which contains all the information that's expected to return from the original GraphQL query.

How do I add (or edit) a mutation?

Coming soon!

Local development

Setup

How to

Development process and standards

Oncall

Technical resources

How-to guides

Environments/Azure

Misc

?

Clone this wiki locally