Catch the highlights of GraphQLConf 2023! Click for recordings. Or check out our recap blog post.
Docs
Getting Started
Combine Multiple Sources

How to: Combine multiple Sources

All the documentation tutorials and guides rely on the "Books", "Authors" and "Stores" example APIs, available in a dedicated repository: graphql-mesh-docs-first-gateway:

  • Books API (REST API)
    • GET /books
    • GET /books/:id
    • GET /categories
  • Authors API (gRPC API)
    • GetAuthor
    • ListAuthors
  • Stores (GraphQL API)
    • stores Query
    • bookSells(storeId: ID!) Query

The getting started "Your first Gateway with Mesh" introduced the configuration of the Books API Source.

This guide will show how to add 2 new sources (Authors and Stores) to achieve the following Gateway setup:

We will go further than just add new Sources by shaping the Unified Schema to accept the following query:

query bestSellersByStore {
  stores {
    id
    name
    bookSells {
      sellsCount
      book {
        id
        title
        author {
          id
          name
        }
      }
    }
  }
}

1. Add the "Authors" Source

The "Authors" Source is a gRPC API: authors-service.

We will use the grpc Handler with the @graphql-mesh/grpc package and configure in our .meshrc.yaml it as follows:

.meshrc.yaml
sources:
  - name: Books
    handler:
      openapi:
        source: http://localhost:3002/openapi.json
  - name: Authors
    handler:
      grpc:
        endpoint: localhost:3003

Similar to the "Books" API configuration, we can either provide the path to the service definition file (here, a Proto file) and the base URL or just the base URL if reflection is enabled. Our "Authors" API supports reflection, so we can just provide the endpoint URL.

We will "clean" the corresponding "Authors" GraphQL Schema later, in the 3rd step, "Shaping the Unified Schema".

2. Add the "Stores" Source

The "Stores" Source is a GraphQL API: stores-service.

We will use the graphql Handler with the @graphql-mesh/graphql package and configure it in our .meshrc.yaml it as follows:

.meshrc.yaml
sources:
  - name: Books
    handler:
      openapi:
        source: http://localhost:3002/openapi.json
  - name: Authors
    handler:
      grpc:
        endpoint: localhost:3003
  - name: Stores
    handler:
      graphql:
        endpoint: http://0.0.0.0:3004/graphql

Since the "Stores" is a GraphQL API, Mesh can leverage introspection to get its schema with just the endpoint URL.

3. Shaping the Unified Schema

The following Mesh Gateway configuration .meshrc.yaml:

.meshrc.yaml
sources:
  - name: Books
    handler:
      openapi:
        source: http://localhost:3002/openapi.json
  - name: Authors
    handler:
      grpc:
        endpoint: localhost:3003
  - name: Stores
    handler:
      graphql:
        endpoint: http://0.0.0.0:3004/graphql

Will gives us a "raw" GraphQL schema design that contains unwanted GraphQL Queries, badly named types, and a lack of hierarchy:

.mesh/schema.graphql

type Query {
  AppController_books: [Book]
  AppController_categories: [Category]
  AppController_book(id: String!): Book
  stores: [Store!]!
  bookSells(storeId: ID!): [Sells!]!
  authors_v1_AuthorsService_GetAuthor(input: authors_v1_GetAuthorRequest_Input): authors_v1_Author
  authors_v1_AuthorsService_ListAuthors(
    input: authors_v1_ListAuthorsRequest_Input
  ): authors_v1_ListAuthorsResponse
  authors_v1_AuthorsService_connectivityState(tryToConnect: Boolean): ConnectivityState
}
 
# …

In order to achieve a clean Unified Schema design that would allow the following Query:

query bestSellersByStore {
  stores {
    id
    name
    bookSells {
      sellsCount
      book {
        id
        title
        author {
          id
          name
        }
      }
    }
  }
}

We will need to configure multiple transforms:

  • Rename the "Books" queries
  • Rename the "Authors" queries
  • Remove the unnecessary root queries: author, book, categories, bookSells
  • Setup some hierarchy between the queries

If you have never configured Transforms before, we advise you to go through the "Your first Gateway with Mesh" tutorial first.

Remove unnecessary Root queries

Also covered in the "Your first Gateway with Mesh" tutorial, removing queries from the Unified Schema is achieved with the @graphql-mesh/transform-filter-schema transform.

To build a clean Unified Schema, we need to remove unnecessary Queries such as:

  • Query.authors_v1_AuthorsService_GetAuthor and Query.authors_v1_AuthorsService_ListAuthors from the "Authors" Source
  • the Query.!authors_v1_AuthorsService_connectivityState is automatically generated by the grpc handler of the "Authors" Source
  • Query.AppController_book and Query.AppController_categories from the "Books" Source
  • Query.bookSells from the "Stores" Source

Our updated .meshrc.yaml is the following:

.meshrc.yaml
sources:
  - name: Books
    handler:
      openapi:
        source: http://localhost:3002/openapi.json
  - name: Authors
    handler:
      grpc:
        endpoint: localhost:3003
  - name: Stores
    handler:
      graphql:
        endpoint: http://0.0.0.0:3004/graphql
transforms:
  - filterSchema:
      filters:
        - Query.!bookSells
        - Query.!{AppController_book, AppController_categories}
        - Query.!authors_v1_AuthorsService_{connectivityState, GetAuthor}

filterSchema takes a filters option that accepts an array of filter rules.

By adding Query.!bookSells, we instruct Mesh to remove the bookSells(...) Query.

You can notice that the filterSchema allows using a bash-like syntax to avoid repetitive configuration with the {..., ...} syntax.

Another way to achieve the same result would be to instruct Mesh only to keep the Query.stores root query as follows:

.meshrc.yaml
sources:
  # ...
transforms:
  - filterSchema:
      filters:
        - Query.stores

More information on the filterSchema Transform on its dedicated documentation page.

Setup hierarchy with nested queries

To support the following Query:

query bestSellersByStore {
  stores {
    id
    name
    bookSells {
      sellsCount
      book {
        id
        title
        author {
          id
          name
        }
      }
    }
  }
}

We need to update the schema to add the following fields:

  • Store.bookSells: [Sells!]!: to get the selling from a given store
  • Sells.book: Book: to get the book of a given store selling record
  • Book.author: authors_v1_Author: to get the author of a book

To achieve this, we will use the additionalResolvers and additionalTypeDefs configuration from Mesh's .meshrc.yaml API.

Add new field to the Schema

Using additionalTypeDefs configuration parameter allows writing GraphQL that will be merged with the Unified Schema definition, allowing us to extend existing types and queries.

Let's say we want to add the Book.author field. We first need to know what is the type of Author.

Since the handlers generate most types, it might be hard to guess their correct spelling (e.g. authors_v1_Author). A quick way to build an additionalTypeDefs is to refer to the generated GraphQL Schema file of each Source that can be found in the .mesh/sources folder.

For example, the GraphQL Schema of the "Authors" Source can be found at .mesh/sources/Authors/schema.graphql file:

schema.graphql
schema {
  query: Query
}
 
type Query {
  authors_v1_AuthorsService_GetAuthor(input: authors_v1_GetAuthorRequest_Input): authors_v1_Author
  authors_v1_AuthorsService_ListAuthors(
    input: authors_v1_ListAuthorsRequest_Input
  ): authors_v1_ListAuthorsResponse
  authors_v1_AuthorsService_connectivityState(tryToConnect: Boolean): ConnectivityState
}
 
type authors_v1_Author {
  id: String
  name: String
  editor: String
}
 
input authors_v1_GetAuthorRequest_Input {
  id: String
}
 
type authors_v1_ListAuthorsResponse {
  items: [authors_v1_Author]
}
 
scalar authors_v1_ListAuthorsRequest_Input
  @specifiedBy(url: "http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf")
 
enum ConnectivityState {
  IDLE
  CONNECTING
  READY
  TRANSIENT_FAILURE
  SHUTDOWN
}
💡
Note: Try running the Mesh Gateway first if the .mesh folder does not exist.

Here we find that an author is described with the authors_v1_Author GraphQL type, which allows us to add the following additionalTypeDefs configuration .meshrc.yaml:

.meshrc.yaml
sources:
  # ...
transforms:
  - filterSchema:
      filters:
        - Query.stores
additionalTypeDefs: |
  extend type Book {
    author: authors_v1_Author
  }

By applying the same process for Store.bookSells and Sells.book, we get the following final additionalTypeDefs configuration .meshrc.yaml:

.meshrc.yaml
sources:
  # ...
transforms:
  - filterSchema:
      filters:
        - Query.stores
additionalTypeDefs: |
  extend type Store {
    bookSells: [Sells!]!
  }
  extend type Sells {
    book: Book
  }
  extend type Book {
    author: authors_v1_Author
  }

Our Unified Schema definition is now updated; we need to point to Mesh on how to resolve our new field's data.

Add resolvers for the new fields

Now, let's describe how Mesh should resolve the data on the newly added type definitions by using the @resolveTo directive.

Again, let's start with the Book.author field.

We need the Book.author field to call the Query.authors_v1_AuthorsService_GetAuthor(input: authors_v1_GetAuthorRequest_Input): authors_v1_Author Query by providing the following @resolveTo directive: .meshrc.yaml:

.meshrc.yaml
sources:
  # ...
additionalTypeDefs: |
  extend type Store {
    bookSells: [Sells!]!
  }
  extend type Sells {
    book: Book
  }
  extend type Book {
    author: authors_v1_Author @resolveTo(
      sourceName: "Authors"                                   # Which source does the target field belong to?
      sourceTypeName: "Query",                                # Which root type does the target field belong to?
      sourceFieldName: "authors_v1_AuthorsService_GetAuthor", # What is the source field name?
      requiredSelectionSet: "{ authorId }",                   # What is the required selection on the target type to resolve the source field?
      sourceArgs: {                                           # What args the source field does need?
        input: {
          id: "{root.authorId}"
        }
      }
    )
  }

Here, the @resolveTo directive explains that Querying Book.author will resolve the data by calling the Query.authors_v1_AuthorsService_GetAuthor from the "Authors" source.

The requiredSelectionSet and sourceArgs ensure that the required arguments are provided (requiredSelectionSet) and adequately mapped to the Source (sourceArgs). requiredSelectionSet ensures that the Book.author selection will contains authorId, so it can be forward to Query.authors_v1_AuthorsService_GetAuthor as the input.id argument.

Applying the same logic to Stores.bookSells and Sells.book gives us a complete .meshrc.yaml configuration.

Run the Mesh Gateway

Our Gateway is now complete; you can start it (along with the Services APIs) by running:

yarn start-multiple-sources

Then, the following query is now properly resolving all the fields:

query bestSellersByStore {
  stores {
    id
    name
    bookSells {
      sellsCount
      book {
        id
        title
        author {
          id
          name
        }
      }
    }
  }
}

Which returns the correct data:

GraphQL Unified Schema with GraphiQL

Congrats! 🎉

You are now familiar with the basics of GraphQL Mesh:

  • Configuring Sources, Handlers, Transforms
  • and shaping the Unified Schema with additionalTypeDefs and additionalResolvers.

Going further

We provide a set of guides that will help you get started on more realistic scenarios, such as:

  • Handling Sources with no definition
  • Customize the Mesh server (caching, Envelop plugins)
  • Deploying a GraphQL Mesh gateway

As well as some advanced guides for more complex schemas:

  • Order of transforms, Sources' Schema, and unified Schema
  • Extending the Unified Schema (additionalResolvers guide)
  • Error handling
  • File upload
  • Subscriptions & Live Queries