Catch the highlights of GraphQLConf 2023!Click for recordings.Or check out our recap blog post.
Tutorial
Basic
Getting Started

Getting Started

In this chapter, you will learn about the GraphQL schema:

  1. How it performs as an API contract between the consumer and the provider
  2. How you can use graphql library as a basic GraphQL execution mechanism
  3. What is a GraphQL operation and how you can use it?

Getting Started with GraphQL

To get a better understanding of how GraphQL works, you can start by reading this tutorial about GraphQL basics (opens in a new tab).

If you are already familiar with the basics of GraphQL, here's a quick overview:

  1. The GraphQL schema is where your GraphQL types are defined.
  2. GraphQL's types are connected using fields, and they form a graph.
  3. The Query, Mutation and Subscription types are special since they act as an entry point to the graph.
  4. The GraphQL schema acts as the data provider, and it offers a set of capabilities the consumer can use.
  5. To get data from a GraphQL schema, you need to write a GraphQL operation (often referred to as query) that selects the data and fields you need.

In this section of the tutorial, you'll write a simple GraphQL schema, and you'll consume it directly, just for the learning process.

Later, you'll replace the direct execution with a GraphQL server (based on HTTP protocol), and you'll add developer tools that will make it super simple to query and access.

Creating your First GraphQL Schema

There are many ways to create a GraphQL schema - in this tutorial, you are going to use the schema-first approach, and build the schema with the @graphql-tools/schema library (take a look at the end of this chapter for different solutions for creating your GraphQL schema).

Start by installing graphql and @graphql-tools/schema library in your project, using the following command:

npm i @graphql-tools/schema graphql

The command above will get you the following libraries installed in the project:

  • graphql is the GraphQL engine implementation.
  • @graphql-tools/schema is a library for creating GraphQL executable schemas.

A GraphQL schema can be written with GraphQL SDL (Schema Definition Language), which is the GraphQL language for defining your API/contract. The actual code and business logic of each field is called a GraphQL resolver.

So let's get started by creating your first, very-simple, GraphQL schema.

To get started with a simple GraphQL schema, you need to create a SDL file defining our contract:

Create a src/schema.ts file with the following content:

src/schema.ts
const typeDefinitions = /* GraphQL */ `
  type Query {
    hello: String!
  }
`

These are our type definitions that describe what data can be retrieved from the schema.

Up next you define the corresponding resolver functions that are used for resolving the data.

src/schema.ts
const typeDefinitions = /* GraphQL */ `
  type Query {
    hello: String!
  }
`
 
const resolvers = {
  Query: {
    hello: () => 'Hello World!'
  }
}

As you can see the resolver map follows the same structure as the type definitions.

The typename (Query) is an object in which the fields (hello) are functions that return the corresponding data of that field.

Now you still need to glue together the type definitions and the resolver map.

src/schema.ts
import { makeExecutableSchema } from '@graphql-tools/schema'
 
const typeDefinitions = /* GraphQL */ `
  type Query {
    hello: String!
  }
`
 
const resolvers = {
  Query: {
    hello: () => 'Hello World!'
  }
}
 
export const schema = makeExecutableSchema({
  resolvers: [resolvers],
  typeDefs: [typeDefinitions]
})

In the code snippet above, you've created or used the following variables:

  • typeDefinitions - this is your GraphQL schema definition. You've created a Query type that exposes a field called hello, of type String
  • resolvers - the resolver functions are part of the GraphQL schema, and they are the actual implementation (code/logic) of the GraphQL schema
  • schema - a combination of the GraphQL SDL and the resolvers. makeExecutableSchema function is in charge of gluing them together into an executable schema we can later use

Now that you have a GraphQL schema, you can use that to fetch data using a GraphQL query operation!

Query the GraphQL schema

As explained before, the GraphQL schema is only your contract, and it exposes the set of all types and capabilities that your API layer can do.

To use your GraphQL schema and consume data from it, you will need to write a GraphQL query operation.

Based on the schema you created before, you can use the following query:

query {
  hello
}

So you don't have to get into all the complexity of running a GraphQL server - you can use this query and run it against your GraphQL schema and get an immediate result.

To query our local schema, even without any fancy GraphQL client or even a GraphQL server, you can use GraphQL's execute function to just run the schema with the query.

Update the code in src/main.ts to contain the following snippet:

src/main.ts
import { execute, parse } from 'graphql'
import { schema } from './schema'
 
async function main() {
  const myQuery = parse(/* GraphQL */ `
    query {
      hello
    }
  `)
 
  const result = await execute({
    schema,
    document: myQuery
  })
 
  console.log(result)
}
 
main()

Now, try to run our project again (either with npm run dev or npm run start), you should see in the output log the following:

{ data: [Object: null prototype] { hello: 'Hello World' } }

You can ignore the [Object: null prototype]. It is just means that the object does not inherit any properties from the global Object prototype and is a security measure that is insignificant for this tutorial.

So What Happened Here?

First, the GraphQL query operation string was parsed it using the parse function of graphql - this will create a DocumentNode object that can later be executed by GraphQL.

The DocumentNode is an abstract syntax tree (AST), an intermediate format that represents the operation string in a more processable friendly way for machines.

The JSON representation of the DocumentNode of the query { hello } operation looks like the following.

{
  "kind": "Document",
  "definitions": [
    {
      "kind": "OperationDefinition",
      "operation": "query",
      "variableDefinitions": [],
      "selectionSet": {
        "kind": "SelectionSet",
        "selections": [
          {
            "kind": "Field",
            "name": {
              "kind": "Name",
              "value": "hello"
            },
            "arguments": []
          }
        ]
      }
    }
  ]
}

Then, the execute function of graphql was called with the following parameters:

  1. schema - this is the GraphQL schema object you previously created.
  2. myQuery - this is the DocumentNode object created based on our GraphQL query.

The return value of execute is the GraphQL result (or, GraphQL response).

The GraphQL engine takes the query, and based on the fields you selected (called the Selection-Set), it runs the resolvers and returns their return value.

The next chapter will teach you how to use your GraphQL schema to create a GraphQL server!

A Word on the GraphQL Schema

At the core of every GraphQL API, there is a GraphQL schema. So, let's quickly talk about it.

💡

Note: In this tutorial, we'll only scratch the surface of this topic. If you want to go a bit more in-depth and learn more about the GraphQL schema as well as its role in a GraphQL API, be sure to check out this (opens in a new tab) excellent article.

GraphQL's schemas are usually written in the GraphQL Schema Definition Language (opens in a new tab) (SDL). SDL has a type system that allows you to define data structures (just like other strongly typed programming languages such as Java, TypeScript, Swift, Go, etc.).

How does that help in defining the API for a GraphQL server, though? Every GraphQL schema has three special root types: Query, Mutation, and Subscription. The root types correspond to the three operation types offered by GraphQL: queries, mutations, and subscriptions. The fields on these root types are called root fields and define the available API operations.

As an example, consider the simple GraphQL schema we used above:

type Query {
  hello: String!
}

This schema only has a single root field, called hello. When sending queries, mutations or subscriptions to a GraphQL API, these always need to start with a root field! In this case, we only have one root field, so there's only one possible query that's accepted by the API.

Let's now consider a slightly more advanced example:

type Query {
  users: [User!]!
  user(id: ID!): User
}
 
type Mutation {
  createUser(name: String!): User!
}
 
type User {
  id: ID!
  name: String!
}

In this case, we have three root fields: users and user on Query as well as createUser on Mutation. An additional definition of the User type is required because otherwise, the schema definition would be incomplete.

What are the API operations that can be derived from this schema definition? Well, we know that each API operation always needs to start with a root field. However, we haven't learned yet what it looks like when the type of root field is itself another object type (opens in a new tab). This is the case here, where the types of the root fields are [User!]!, User and User!. In the hello example from before, the type of the root field was a String, which is a scalar type (opens in a new tab).

When the type of root field is an object type, you can further expand the query (or mutation/subscription) with fields of that object type.

Here are the operations that are accepted by a GraphQL API that implements the above schema:

# Query for all users
query {
  users {
    id
    name
  }
}
 
# Query a single user by their id
query {
  user(id: "user-1") {
    id
    name
  }
}
 
# Query multiple users and a single user by their id
query {
  users {
    id
    name
  }
  user(id: "user-1") {
    id
    name
  }
}
 
# Create a new user
mutation {
  createUser(name: "Bob") {
    id
    name
  }
}

There are a few things to note:

  • In these examples, we always query id and name of the returned User objects. We could potentially omit either of them. Note, however, that when querying an object type, it is required that you query at least one of its fields in a selection set.
  • For the fields in the selection set, it doesn't matter whether the type of the root field is required or a list. In the example schema above, the three root fields all have different type modifiers (opens in a new tab) (i.e. different combinations of being a list and/or required) for the User type:
    • For the users field, the return type [User!]! means it returns a list (which itself cannot be null) of User elements. The list can also not contain elements that are null. So, you're always guaranteed to either receive an empty list or a list that only contains non-null User objects.
    • For the user(id: ID!) field, the return type User means the returned value could be null or a User object.
    • For the createUser(name: String!) field, the return type User! means this operation always returns a User object.

You will learn how to write the corresponding resolver function maps for addition type fields in the following chapters.

Additional Resources

As mentioned at the beginning of this chapter, there are many ways to build and create your GraphQL schema.

Here are a few popular open-source libraries: