• Tutorial
  • Basic
  • A Simple Query

A Simple Query

In this section, you are going to implement the first GraphQL object types and fields that provide the functionality of a Hacker News clone for querying a feed of links that were posted by other users.

Extending the Schema Definition

Let's start by implementing a feed field on the Query root types which allows retrieving a list of Link elements. In general, when adding a new feature to the GraphQL Schema, the process will look pretty similar every time:

  1. Extend the GraphQL schema definition (SDL) with new root fields (on the Query, Mutation or Subscription object types) and new object types and fields, if needed
  2. Implement corresponding resolver functions for the added fields/object types

This process is also referred to as the schema-driven or schema-first development workflow.

So, let's go ahead and tackle the first step, extending the GraphQL schema definition.

In src/schema.ts, update the GraphQL schema to look as follows:

src/schema.ts
const typeDefinitions = /* GraphQL */ `
  type Query {
    info: String!
    feed: [Link!]!
  }
 
  type Link {
    id: ID!
    description: String!
    url: String!
  }
`

Pretty straightforward, right? You're defining a new Link object type that represents the links that can be posted to Hacker News. Each Link entity has an id, a description, and a url field. You're then adding another field to the root Query type that allows you to retrieve a list of Link elements. This list is guaranteed to never be null (if there isn't anything, the list must be empty) and never contain any elements that are null - that's what the two exclamation marks are for.

💡

If you wish to read more about GraphQL and nullability, go ahead to GraphQL official documentation.

Implement Resolver Functions

The next step is to implement the resolver function for the feed query field. One thing we haven't mentioned yet is that not only root fields, but virtually all fields on the types in a GraphQL schema have resolver functions. So, you'll add resolver functions for the id, description, and url fields of the Link type as well.

In src/schema.ts, add a new list with dummy data as well and update the resolvers to look as follows:

src/schema.ts
// 1
type Link = {
  id: string
  url: string
  description: string
}
 
// 2
const links: Link[] = [
  {
    id: 'link-0',
    url: 'https://graphql-yoga.com',
    description: 'The easiest way of setting up a GraphQL server',
  },
]
 
const resolvers = {
  Query: {
    info: () => `This is the API of a Hackernews Clone`,
    // 3
    feed: () => links,
  },
  // 4
  Link: {
    id: (parent: Link) => parent.id,
    description: (parent: Link) => parent.description,
    url: (parent: Link) => parent.url,
  },
}

Let's walk through the numbered comments again:

  1. The Link type defines the TypeScript object structure that we wish to use in our code
  2. The links variable is used to store the links at runtime. For now, everything is stored only in-memory rather than being persisted in a database
  3. You're adding a new resolver for the feed root field. Notice that a resolver always has to be named exactly after the corresponding field from the schema definition
  4. Finally, you're adding three more resolvers for the fields on the Link type from the schema definition. We'll discuss what the parent argument that's passed into the resolver here is in a bit

Go ahead and test the implementation by running the server and navigating to http://localhost:4000/graphql in your browser. If you expand the documentation of the GraphiQL, you'll notice that another field on the root Query object type called feed is now available:

another Query root type field called feed

Try it out by sending the following query operation via GraphiQL:

query {
  feed {
    id
    url
    description
  }
}

Awesome, the server responds with the data you defined in links:

{
  "data": {
    "feed": [
      {
        "id": "link-0",
        "url": "https://graphql-yoga.com",
        "description": "The easiest way of setting up a GraphQL server"
      }
    ]
  }
}

data returned from the server

Feel free to play around with the query by removing any fields from the selection set and observing the responses sent by the server.

The Query Resolution Process

Let's now quickly talk about how a GraphQL server resolves incoming requests. As you already saw, a GraphQL operation contains a selections set of fields that have their source in the type definitions of the GraphQL schema.

Let's consider the following query operation from above again:

query {
  feed {
    id
    url
    description
  }
}

All four fields specified in the query operations selection set (feed, id, url, and description) can also be found inside the schema definition. You also learned that every field inside the schema definition is backed by one resolver function whose responsibility it is to return the data for precisely that field.

Can you imagine what the query resolution process looks like now? Effectively, the GraphQL server has to invoke all resolver functions for the fields that are selected in the operation and then package up the response according to the selection sets shape. The resolution thus merely becomes a process of orchestrating the invocation of the resolver functions!

Right now all the resolvers for the Link type follow a very simple and trivial pattern:

Link: {
  id: (parent: Link) => parent.id,
  description: (parent: Link) => parent.description,
  url: (parent: Link) => parent.url
}

First, it's important to note that every GraphQL resolver function receives four input arguments. As the remaining three are not needed in our scenario right now, we're simply omitting them. Don't worry, you'll get to know them soon.

The first argument, commonly called parent (or sometimes root) is the result of the previous resolver execution level. Hang on, what does that mean? 🤔

Well, as you already saw, GraphQL operation selection sets can be nested. Each level of nesting (i.e. nested curly braces) corresponds to one resolver execution level. The above query operation. Therefore, has two of these execution levels.

On the first level, it invokes the Query.feed resolver and returns the entire data stored in links. For the second execution level, the GraphQL server is smart enough to invoke the resolvers of the Link type (because thanks to the schema, it knows that feed returns a list of Link elements) for each element inside the list that was returned on the previous resolver level. Therefore, in all the three Link resolvers, the incoming parent object is the element inside the links list.

💡

Note: To learn more about this, check out this article.

💡

The implementation of the Link resolvers is trivial, you can omit them and the server will work in the same way as it did before 👌 We just wanted you to understand what's happening under the hood 🚗.

Last updated on September 19, 2022