Migrate to GraphQL Mesh v1
How to: Configure Sources with no definition
GraphQL Mesh provides an extensive range of Handlers (OpenAPI, gRPC, SOAP, GraphQL, and even Databases!), however, you might try to configure a Source that does not provide an API definition.
We will again use the “Books” example REST API:
- Books API (REST API)
GET /books
GET /books/:id
GET /categories
However, let’s pretend that the “Books” API is not providing any OpenAPI definition file:
Once again, GraphQL Mesh gets you covered with the @graphql-mesh/json-schema
handler that will
help provide a definition of the API.
An overview of the jsonSchema
handler
A jsonSchema
handler configuration must provide a set of operations
that define the Query and
Mutation to expose.
A standard jsonSchema
handler configuration will look at the following:
sources:
- name: MyApi
handler:
jsonSchema:
endpoint: https://some-service-url/endpoint-path/
operations:
- type: Query
field: users
path: /users
method: GET
responseSample: ./samples/users.json
operations
defines a set of GraphQL queries or mutations mapped to some API endpoints (path
,
method
)
Above, the users
Query targets the GET /users
endpoint.
Finally, we provide responseSample
that points to a sample file of the Query response.
By using the responseSample
file, GraphQL Mesh will be able to generate a GraphQL definition of
the users
Query.
Let’s put it in practice with our “Books” REST API GET /book/:id
endpoint.
Configuring our “Books” REST API
You will find all the source code of the below example in the dedicated repository:
graphql-mesh-docs-first-gateway
.
After installing the @graphql-mesh/json-schema
package, a good starting point for our “Books” REST
API jsonSchema
handler configuration would be the following
.meshrc.yaml
:
sources:
- name: Books
handler:
jsonSchema:
endpoint: http://localhost:3002
We need to provide some operations
for the GET /book/:id
along with some sample data.
We can get some sample data by running the following commands (at the root of the project):
- Build and start the Books REST API
yarn workspace books-service run build # compiles TypeScript
yarn workspace books-service run start # runs the Nest server
- In a separate terminal, run the following
curl
command:
curl --location --request GET 'http://localhost:3002/books/1' > packages/single-source-no-source-definition/samples/book-1.json
Which will create the following
packages/single-source-no-source-definition/samples/book-1.json
file:
{ "id": "1", "title": "Dune", "authorId": "0", "categoryId": "0" }
We can now provide use this sample file to configure our Query.book
operation as follows
.meshrc.yaml
:
sources:
- name: Books
handler:
jsonSchema:
endpoint: http://localhost:3002
operations:
- type: Query
field: book
path: /books/{args.id}
method: GET
responseSample: ./samples/book-1.json
{arg.<name>}
conventionLet’s build and start our Gateway by running:
yarn run single-source-no-source-definition
Once built, you will find the Unified Schema definition at
packages/single-source-no-source-definition/.mesh/schema.graphql
:
type Query {
book(id: ID): query_book
}
type query_book {
id: String
title: String
authorId: String
categoryId: String
}
Note: The query_book
type has been generated thanks to our
packages/single-source-no-source-definition/samples/book-1.json
sample file.
We can open the browser at and http://0.0.0.0:4000 try the following Query with GraphiQL:
{
book(id: "1") {
id
title
authorId
}
}
We successfully added a Source without definition! 🎉
Going further
Rename generated types
Our Query.book
jsonSchema
configuration generates a query_book
type which is poorly named and
using snake_case.
The responseTypeName
allows us to provide a type name that we will be used for the generated type,
as follows
.meshrc.yaml
:
sources:
- name: Books
handler:
jsonSchema:
endpoint: http://localhost:3002
operations:
- type: Query
field: book
path: /books/{args.id}
method: GET
responseSample: ./samples/book-1.json
responseTypeName: Book
Which produces the following Unified Schema:
type Query {
book(id: ID): Book
}
type Book {
id: String
title: String
authorId: String
categoryId: String
}
The same parameter exists for request: requestTypeName
(see “Mutations”).
Queries/Mutations arguments
We saw that operations[].path
can take arguments using syntax similar to string interpolation
.meshrc.yaml
:
sources:
- name: Books
handler:
jsonSchema:
endpoint: http://localhost:3002
operations:
- type: Query
field: book
path: /books/{args.id}
method: GET
responseSample: ./samples/book-1.json
GraphQL Mesh will assign the ID
type to all arguments by default.
Now, let’s say that the “Books” API introduces a search endpoint as follows:
GET /books/search?q=<query>&categoryId=<categoryId>
We would need the query
argument to be of type String
(not ID
).
To achieve this, we will leverage the argTypeMap
parameter as follow:
sources:
- name: Books
handler:
jsonSchema:
endpoint: http://localhost:3002
operations:
- type: Query
field: search
path: /books/search?q={args.query}&categoryId={args.categoryId}
method: GET
responseSample: ./samples/book-search.json
argTypeMap:
query:
type: string
nullable: false
Now, GraphQL Mesh knows that the type definition of our search query is:
Query.search(query: String!, categoryId: ID)
.
Mutations
Since Mutations are most likely to rely on POST
requests, we need to provide both a
requestSample
and a responseSample
parameters.
For example, if our “Books” API exposed a POST /books
, we could define the following
Mutation.addBook(…)
mutation:
sources:
- name: Books
handler:
jsonSchema:
endpoint: http://localhost:3002
operations:
- type: Mutation
field: addBook
path: /books
method: POST
requestSample: ./samples/add-book-request.json
responseSample: ./samples/add-book-response.json
Query
/Mutation
with multiple response shapes
Providing responseSample/requestSample
is an efficient way to configure a Source without API
definition.
However, the sample files might not always represent all the variants of a given endpoint.
For example, our previous POST /books
endpoint could return a totally different response shape
depending on the scenario:
Successfully book creation response
{
"id": "1",
"title": "Dune",
"authorId": "0",
"categoryId": "0"
}
Unauthorized response
{ "error": "Unauthorized" }
Duplicate book response
{
"error": "Duplicate",
"duplicateOf": {
"id": "1",
"title": "Dune",
"authorId": "0",
"categoryId": "0"
}
}
Relying on curl
to get a sample of all those scenarios is impossible.
In that case, we will to provide a responseSchema
parameter that will point to a JSON Schema file:
sources:
- name: Books
handler:
jsonSchema:
endpoint: http://localhost:3002
operations:
- type: Mutation
field: addBook
path: /books
method: POST
requestSample: ./samples/add-book-request.json
responseSchema: ./schemas/add-book-response.json#/definitions/AddBookOutput
This guide doesn’t provide any materials to learn JSON Schema (this would require multiple guides).
However, you can get started on the official JSON Schema tutorial.
Our ./schema/add-book-response.json
would look as follows:
{
"type": "object",
"properties": {},
"definitions": {
"AddBookOutput": {
"title": "AddBookOutput",
"oneOf": [
{
"$ref": "#/definitions/Book"
},
{
"$ref": "#/definitions/AddBookOutputUnauthorized"
},
{
"$ref": "#/definitions/AddBookOutputDuplicate"
}
]
},
"Book": {
"title": "Book",
"type": "object",
"required": ["id"],
"properties": {
"id": {
"type": "string"
},
"title": {
"type": "string"
},
"authorId": {
"type": "string"
},
"categoryId": {
"type": "string"
}
}
},
"AddBookOutputUnauthorized": {
"title": "AddBookOutputUnauthorized",
"type": "object",
"required": ["error"],
"properties": {
"error": {
"type": "string"
}
}
},
"AddBookOutputDuplicate": {
"title": "AddBookOutputDuplicate",
"type": "object",
"required": ["error", "duplicateOf"],
"properties": {
"error": {
"type": "string"
},
"duplicateOf": {
"$ref": "#/definitions/Book"
}
}
}
}
}
Building our Mesh Gateway will generate the following Unified Schema:
directive @oneOf on INPUT_OBJECT | FIELD_DEFINITION
type Mutation {
addBook(id: ID): AddBookOutput
}
union AddBookOutput = Book | AddBookOutputUnauthorized | AddBookOutputDuplicate
type Book {
id: String!
title: String
authorId: String
categoryId: String
}
type AddBookOutputUnauthorized {
error: String!
}
type AddBookOutputDuplicate {
error: String!
duplicateOf: Book!
}
Note that you can also leverage the responseByStatusCode
parameter to provide a schema per HTTP
Status Code, as follows:
operations:
# …
responseByStatusCode:
404:
responseSchema: ...
200:
responseSchema: ...