The Guild LogoThe Guild Monogram
GraphQL Mesh

GraphQL Mesh

Query anything, run anywhere.

Contact Us

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 gots 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:

Example .meshrc.yaml

sources: - name: MyApi handler: jsonSchema: baseUrl: 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 of the 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: baseUrl: 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 ):

  1. Build and start the Books REST API
yarn workspaces run build # compiles TypeScript yarn workspace books-service run start
  1. 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","categorieId":"0"}

We can now provide use this sample file to configure our Query.book operation as follows:

.meshrc.yaml

sources: - name: Books handler: jsonSchema: baseUrl: http://localhost:3002 operations: - type: Query field: book path: /books/{args.id} method: GET responseSample: ./samples/book-1.json

Note: URL argument should be defined using the {arg.<name>} convention


Let'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 categorieId: 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: baseUrl: 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 categorieId: 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: baseUrl: 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:

Example .meshrc.yaml

sources: - name: Books handler: jsonSchema: baseUrl: 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: String!

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:

Example .meshrc.yaml

sources: - name: Books handler: jsonSchema: baseUrl: 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:

Successfull book creation response

{ "id": "1", "title": "Dune", "authorId": "0", "categorieId": "0" }

Unauthorized response

{"error": "Unauthorized"}

Duplicate book response

{ "error": "Duplicate", "duplicateOf": { "id": "1", "title": "Dune", "authorId": "0", "categorieId": "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:

Example .meshrc.yaml

sources: - name: Books handler: jsonSchema: baseUrl: 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: responseSchmea: ..