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
QuerybookSells(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:
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:
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
:
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:
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
andQuery.authors_v1_AuthorsService_ListAuthors
from the "Authors" Source- the
Query.!authors_v1_AuthorsService_connectivityState
is automatically generated by thegrpc
handler of the "Authors" Source Query.AppController_book
andQuery.AppController_categories
from the "Books" SourceQuery.bookSells
from the "Stores" Source
Our updated
.meshrc.yaml
is the following:
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:
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 storeSells.book: Book
: to get the book of a given store selling recordBook.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 {
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
}
.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
:
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
:
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
:
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:
Congrats! 🎉
You are now familiar with the basics of GraphQL Mesh:
- Configuring Sources, Handlers, Transforms
- and shaping the Unified Schema with
additionalTypeDefs
andadditionalResolvers
.
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