2 - GraphQL Core Concepts: Schema, Resolvers, Query, Apollo
Tags: graphql, webdev, tutorial
Published: 2023-11-23 16:39:52

In this post, we will talk about the core concepts of GraphQL:
- Schema,
- Resolvers,
- Query

In the next post, we will dive deeper into:
- Mutations, 
- Subscriptions. 

As said earlier, unlike REST APIs, GraphQL can use a single endpoint to fetch data from a GraphQL API. 

In this sentence, you see the [dual nature of GraphQL](https://graphql.org/learn/): "_GraphQL is a query language for your API, and a server-side runtime for executing queries_".

This is important to know because some terminology can be confusing e.g. a client queries the server is simply sending a request in a specific query format. The Query type defines the format from the GraphQL server.

In this post:
- lowercase query defines an HTTP POST request.
- Query with capital "C" indicates the Query type object in the GraphQL server.

As a side note, all GraphQL requests to the server are HTTP POST.

Let's see some examples to clarify the core concepts.

## Schema
The schema is the rulebook that explains what is possible to retrieve from a GraphQL API. Formally, "_the schema represents data objects with object types_", [graphql.com](https://graphql.com/learn/schema/#inspecting-the-schema).

A graphQL schema is defined in a file in your backend. 

For the sake of simplicity, I initialized a simple backend with `npm init` and I defined a schema in a file called index.js.
  
Here is a schema defining a Fruit.

```graphql
// index.js

type Fruit {
  id: ID!
  name: String!
  quantity: Int!
  price: Int!
}
```

This is just an example. But it is enough to understand that a graphQL schema roughly resembles a TypeScript type (to me at least).

To be more precise, the object type Fruit, determines that fruits can have four fields: id, name, quantity, and price. 

Each field has a type (scalar type).
- id has type ID, which is a "special" String because GraphQl makes sure it is unique.
- name is of type String,
- quantity is of type integer,
- price is of type integer

In GraphQL the type of a field (e.g. scalar type) can be one of the following: String, Int, Float, Boolean, and ID.

All fields are required. 

The `!` symbol indicates that the field is mandatory. In more formal terms, the field should never return null. Note that an empty list `[]` is empty and not null.

Read more about [types (object, scalar, and lists)](https://graphql.com/learn/scalars-objects-lists/).


"_To let us query anything at all, a schema requires first and foremost the Query type. [...] the Query type acts as the front door to the service_", [graphQL](https://graphql.com/learn/schema/#using-the-query-type).

The Query type is a special object type (like Fruit).
So, the first Query we add is

```graphql
// index.js

type Fruit {
  id: ID!
  name: String!
  quantity: Int!
  price: Int!
}

type Query {
  allFruits: [Fruit!]!
}
```

### Testing with Apollo Server
Since we are defining these things server-side, we can quickly test them by installing the Apollo server, as follows:

```
npm install @apollo/server graphql
```

Once the installation is completed, change `index.js` to require ApolloServer. We are also including some dummy data from [graphql.com](https://graphql.com/learn/schema/#using-the-query-type).

At the bottom, we are instantiating a new ApolloServer to which we pass the schema.

Finally, we start the server with startStandaloneServer.

```javascript
const { ApolloServer } = require('@apollo/server')
const { startStandaloneServer } = require('@apollo/server/standalone')

let fruits = [
  {
    "id": "F2",
    "name": "blueberry",
    "price": 2,
    "quantity": 19
  },
  {
    "id": "F3",
    "name": "pear",
    "price": 79,
    "quantity": 36
  },
  {
    "id": "F1",
    "name": "banana",
    "price": 44,
    "quantity": 84
  }
]

const typeDefs = `
  type Fruit {
    id: ID!
    name: String!
    quantity: Int!
    price: Int!
  }

  type Query {
    allFruits: [Fruit!]!
  }
`
const server = new ApolloServer({
  typeDefs,
})

startStandaloneServer(server, {
  listen: { port: 4000 },
}).then(({ url }) => {
  console.log(`Server available at ${url}`)
})

```

Now, if you run `node index.js` you will start the GraphQL server in your terminal. You should see something like "Server available at http://localhost:4000/".

Click on http://localhost:4000/ to open Apollo Sandbox where you can test queries etc.

![Apollo Sandbox](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7b97x5c0453j1fxbcdhb.png)

## Resolver: Connecting client queries to schema
The Operation area on Apollo Sandbox allows us to try queries (query the GraphQL server) as if we would send requests from a client. 

As an example, the following query (HTTP request) should return all fruit names, but it will fail.
 
```graphql
query ExampleQuery {
  allFruits {
    name
  } 
}
```
In the schema we defined the object type Query as follows:

```graphql
type Query {
    allFruits: [Fruit!]!
  }
```

but how do we get from the fruits array to allFruits?

We use resolvers.

### Resolver Example 1

Here is one resolver:

```graphql
const resolvers = {
  Query: {
    allFruits: () => fruits,
  },
};
```
So our `index.js` becomes:

```
...
let fruits = [...]

const typeDefs = `
  type Fruit { ... }

  type Query {
    allFruits: [Fruit!]!
  }
`
const resolvers = {
  Query: {
    allFruits: () => fruits,
  },
};

const server = new ApolloServer({
  typeDefs,
  resolvers,
});
...
```
Why all of this?
Let's look at it from the client perspective. The client sends a request (a query):

```graphql
query ExampleQuery {
  allFruits {
    name
  } 
}
```
 
1. The request wants the "name" Field for all objects in allFruits
2. The Query type in the schema acts as the front door and allows for queries that want `allFruits`. It also defines that queries to `allFruits` should return a non-null array of Fruit e.g. `[Fruit!]!`
3. The resolver picks up the request and returns the fruits array `allFruits: () => fruits`.

### Resolver Example 2

Let's make another example assuming we want to get the number of fruit objects in the fruits array.

With a REST API, we would need to fetch the whole array and once we receive it, we would do something like `fruits.length` in the client device.

With GraphQL we can define another field in the schema type Query. The number of fruits is going to be an integer so the schema type Query becomes:

```graphql
type Query {
    allFruits: [Fruit!]!,
    fruitsCount: Int!
  }
```
Now we created a new "door" called `fruitsCount` that will return an integer, e.g. the number of fruit objects in the fruits array.

Where do we get this number? We create a resolver for the `fruitsCount` field.

So that when a client pings the GraphQL API with a `fruitsCount` query, we return the number of fruit objects in the fruits array.

Let's update the resolver as follows:

```javascript
const resolvers = {
  Query: {
    fruitsCount: () => fruits.length,
    allFruits: () => fruits,
  },
};

``` 

Now, if a client uses the following query, it will only get an object containing a number:

```graphql
query ExampleQuery {
  fruitsCount
}
```

### Resolver Example 3
Let's assume we want to find a fruit by its name.

We start by updating the type Query (the "door") in the schema:

```graphql
type Query {
    fruitsCount: Int!
    allFruits: [Fruit!]!
    findFruit(name: String!): Fruit
  }
```

We then update the resolver to find and return a specific fruit  

```javascript
const resolvers = {
  Query: {
    fruitsCount: () => fruits.length,
    allFruits: () => fruits,
    findFruit: (root, args) => fruits.find((fruit) => fruit.name === args.name),
  },
};
```
The findFruit resolver is a bit different but you can [read more about resolvers here](https://the-guild.dev/graphql/tools/docs/resolvers#resolver-function-signature).

A client could query the GraphQL API as follows to obtain the total number of fruit objects and some specific data on the "pear" object.

```graphql
query ExampleQuery {
  fruitsCount, 
  findFruit(name: "pear"){
    id,
    name, 
    price,
  }
}
```
The JSON response would be:

```json
{
  "data": {
    "fruitsCount": 3,
    "findFruit": {
      "id": "F3",
      "name": "pear",
      "price": 79,
      "quantity": 36
    }
  }
}
```


## Query

GraphQL uses queries to retrieve data from a GraphQL API. Each query builds on Types and Fields. In the following example ([from grapql.com](https://graphql.com/learn/the-query/#running-a-query)) we run a Query to get fruits (Type) and prices (Field):
- We want to get data of type "fruits"
- For each fruit we get, we want the price field

"_This query syntax begins with the query keyword, followed by an operation name that describes the request's purpose—like GetFruitPrices._"

```graphql
query GetFruitPrices {
  fruits {
    price,
    name
  }
}
```

The response is:
```json
{
  "data": {
    "fruits": [
      { "price": 44, "name": "banana" },
      { "price": 2, "name": "blueberry" },
      { "price": 79, "name": "pear" }
    ]
  }
}
```

But how do we know what we can retrieve from a GraphQL API?
In the Schema.