27

What is the purpose of root query field viewer in GraphQL?

Based on this article, viewer could be used to accept a token parameter so we can see who is currently logged in.

How should I implement it?

jonathancardoso
  • 9,319
  • 6
  • 49
  • 63
Sibelius Seraphini
  • 4,023
  • 7
  • 30
  • 52
  • 1
    there is no need for viewer pattern anymore in Relay Modern – Sibelius Seraphini Jun 06 '17 at 13:08
  • It's strange that I can't find any concrete example (with SQL or Mongo), just a lot of abstraction on this topic. Hope there is a real example, just want to see the implementation. – Tokenyet Mar 23 '21 at 09:04
  • I don't recommend Viewer pattern anymore, it was a Relay Classic thing, you should use a me: UserType field instead check Relay Workshop for a better example https://github.com/sibelius/relay-workshop/blob/master/packages/server/src/schema/QueryType.ts – Sibelius Seraphini Mar 26 '21 at 20:49

2 Answers2

33

Purpose of viewer root query field

viewer is not something GraphQL or Relay-specific. Most web applications serve some purposes of its users or viewers. The top level entity to model the various data served to the user can be named as viewer. You can also name it user. For example, the Relay todo example has a viewer root query field:

viewer: {
  type: GraphQLUser,
  resolve: () => getViewer(),
},

We may also do without viewer. For instance, Relay starwars example does not have any viewer root query field.

In short, having this viewer as a root query field of the GraphQL schema enables us to provide data based on the current user.

Implementation: How to use authentication token together with viewer

My answer follows what is already described in your mentioned article. The steps are:

  1. On the server-side, create a mutation to obtain an authentication token. Let's name it LoginMutation. Input to this mutation are the user credentials and the output is an authentication token.

  2. On the client-side, if you use relay framework, implement a client-side mutation. After the mutation is successful, store the authentication token.

  3. On the client-side Relay code, add authToken parameter for your viewer queries. The value of authToken is the authentication token received after successful login mutation.

An alternative

As already mentioned in the article, an alternative way of authenticating user is to do it outside of GraphQL. You may want to see two excellent answers this and this for details.

Jonas Helfer wrote a two-part article on this, which you'll find very useful: Part 1, Part 2

Community
  • 1
  • 1
Ahmad Ferdous
  • 3,161
  • 12
  • 28
3

The idea behind the viewer field (design pattern) was to group the top-level query fields that are only relevant to the currently logged in user. For example:

# EXAMPLE 1

quer {
  viewer {
    stories { ... } # the list of published stores as well as drafts (current user)
  }

  stories { ... }   # the list of published stories (all users)
}

This currently logged user data was either merged into viewer field itself or nested under it:

# EXAMPLE 2

query {
  viewer {
    id
    email
    displayName
    stories { ... }
  }
}

# EXAMPLE 3

query {
  viewer {
    me { id email displayName }
    stories { ... }
  }
}

All three examples above can be simplified by removing the viewer field altogether and still have the exact same functionality (recommended):

query {
  # The currently logged in user or NULL if not logged in
  me {
    id
    email
    displayName
  }

  # Published stories only (all users)
  stories {
    ...
  }

  # Published stories as well as drafts (the current user)
  stories(drafts: true) {
    ...
  }
}

You can find the complete example in Node.js API Starter Kit which can be used either as a reference project or a seed/template for new developments. See api/schema.ts.

Konstantin Tarkus
  • 35,208
  • 14
  • 127
  • 117
  • Why do you (or who) recommend not having a viewer, but instead independent queries? (still learning) – Will Cowan Oct 25 '20 at 10:03
  • Because it's simpler, cleaner; while introducing the "viewer" field sounds like trying to solve some hypothetical (non-existent) problem. – Konstantin Tarkus Nov 16 '20 at 10:48
  • I don't agree with this recommendation. Authenticated user and any other regular user have very different concerns and different problems to solve, in particular with what fields should be visible. You shouldn't be able to query for the SSN of any user on the platform, but you should be able to query SSN of yourself logged in. – Deal Feb 01 '21 at 20:45
  • @Deal permissions for individual fields are enforced inside of entity GraphQL types (it has nothing to do with the "viewer" top-level field). Here is an example: https://github.com/kriasoft/nodejs-api-starter/blob/main/api/types/user.ts#L33-L40 – Konstantin Tarkus Feb 08 '21 at 07:30
  • I still disagree @KonstantinTarkus. Your example is trivial and over time your definition of user becomes muddy. Say now you need to introduce the concept of 'team members', which are technically users, and you should be able to query for their email addresses if you are on the same team. Your resolver is going to balloon with numerous if statements trying to figure out if the user can view that. The real solution is a User interface, and objects that implement that interface: Viewer, TeamMember, and so on. – Deal Feb 18 '21 at 17:19
  • @KonstantinTarkus I recommend you give "Production Ready GraphQL" book a read: https://book.productionreadygraphql.com/. Written by Marc-Andre. This is briefly mentioned in the book. – Deal Feb 18 '21 at 17:20
  • Again, your example above has nothing to do with the "viewer" field concept. If you want to introduce different user types, they will be pulled from a separate top-level field, such as "users" or "members". I see no contradictions. You can satisfy the exact same business requirements in a simpler way. – Konstantin Tarkus Mar 04 '21 at 13:47