GraphQL schema design: the viewer field

Exploring the viewer concept in a GraphQL schema.

Arnaud Bezançon
WorkflowGen
6 min readOct 12, 2017

--

You can find the viewer field as one of the query root fields in a lot of GraphQL-based APIs such as the GitHub API v4. Its definition and usage can lead to some issues, though, especially when we have to decide which subfields have to be exposed in the viewer field and which ones have to be available at the root level.

In GraphQL, the viewer concept appears to have been introduced in Relay, which requires a viewer root field (although this is now optional in modern Relay). This idea also exists in the REST world where you can find a /me endpoint in some Web APIs.

The viewer field represents the currently logged-in user; its subfields expose data that are contextual to the user.

Let’s define a simple GraphQL schema to explore the viewer concept:

In this schema, the viewer is defined with the User type. There is no actual Viewer type; rather, the viewer is just a specific User instance.

When you want to add new subfields to the viewer, you have to alter the User type definition.

Let’s define the currently logged-in users as having the following attributes:

  • id: “VXNlcjoxMTEx”
  • userName: “aldous”

This query will return the same user information three times:

Looking at the query above, it’s clear that the viewer root field provides direct access to the current user information without the need to specify additional arguments, which you might not have in your frontend.

Security context and User context

A GraphQL query is executed in the security context of the currently logged-in user. One common pitfall you might encounter is the need to consider that the viewer root field has a special security context.

In a GraphQL query, every field is resolved in the security context of the logged-in user.

In the previous query, the viewer, user(username:“aldous”) and node(id:“VXNlcjoxMTEx”) resolvers have to check if the logged-in user has the right to access the specified user and see the associated subfields. In our GraphQL API implementation, the same getUser operation is used to return the user data for those three resolvers. This operation verifies if the logged-in user has the required access right to fetch the requested user. When getUser is called by the viewer resolver, the logged-in user and the user to retrieve are the same.

It’s more precise to talk about the user context instead of the viewer context to avoid potential confusion with the security context of the logged-in user. The viewer root field is just one method to retrieve a user.

Security context: Logged-in user
User context: specified (e.g. user(username:“aldous”)) or implicit user (e.g. viewer)

In the User type we can find fields corresponding to the user attributes: id, lastName, userName, email, company, etc. Some visibility rules might apply to attributes; for example, a user cannot access certain details about another user.

You can also have fields about user memberships as groups. The example below shows the schema updated with the user’s groups (we use arrays for the groups field to simplify the example, but a paginated list is more efficient):

If you want to retrieve a specific group you can use the node root field, but you might need to fetch a group by name, in which case you’ll need to add a new root field: group(name: String!) : Group.

If you want to list all of the existing groups and users you have to add other root fields:

Let’s look at this query:

The viewer who is the logged-in user can see their associated groups, but access to other users (such as “alice”) or to all the groups depends on the logged-in user security context. For example, a standard user can have access only to the groups associated to their account, but cannot list other users’ groups.

In this simple schema, we can see that the viewer management will naturally impact the User type while also introducing new query root fields with their associated authorization rules.

What’s inside and what’s outside the viewer field?

In the viewer, you might want to have access to the corresponding user contexts:

  • identity and attributes (e.g. id, name, company)
  • membership (e.g. groups, participants)
  • relationship (e.g. posts, projects, actions)

The viewer/User subfields are the logical connections (beyond the authorization rules) between the user and the other schema entities.

As root fields outside the viewer/User, you might find:

  • The singular form of the entities for direct access (e.g. node, group, participant, post, project, action). These fields are useful when searching for an item by an alternative key, such as fetching a user by username or a project by name.
  • The plural form of the entities to search items (e.g. nodes, users, groups, participants, posts, projects, actions). An alternative solution is to have a generic search field (e.g. GitHub API v4) that can return multiple object types.

These guidelines are not carved in stone, and you might find your own logic to design your schema.

Naming

Naming is quite important to make your GraphQL API intuitive; one way is to provide explicit descriptions of the relationships between the user and the other entities.

For example, in the GitHub API v4, you can find the subfields related to repositories in the User type:

  • repository (find a repository among the ones owned by the user)
  • repositories (list of repositories that the user owns)
  • pinnedRepositories
  • contributedRepositories
  • starRepositories

and at the query root:

  • repository (look up a given repository by its owner and repository name)
  • search (perform a search across resources so that it can return repositories)

An alternative design solution for pinnedRepositories, contributedRepositories and starRepositories would be to reuse the existing repositories field with additional filters; for example, repositories (role: OWNER) or repositories (role: CONTRIBUTOR). Without a role filter defined, the repositories subfield returns the UNION of all the roles.

Each of these solutions has its advantages and drawbacks. A generic approach can make the list of fields shorter, but it can also impact its readability. You therefore have to find the right balance, and try to use naming that will remain consistent as your schema grows.

Conclusion

With the new generation of user-centric and contextual apps, the viewer field is a key element of a GraphQL API. Schema design is always a challenge, but working with in parallel with the frontend team helps to focus on developer experience as well as to detect potential security issues.

As more and more developers and companies adopt GraphQL, the sharing of schema design best practices will accelerate GraphQL API implementations.

--

--

Arnaud Bezançon
WorkflowGen

Full Stack Architect, Creator of WorkflowGen, Advantys co-founder and CTO.