• Docs
  • Getting Started
  • Configure Sources with no definition

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:

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

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

book-1.json
{ "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:

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

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:

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

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

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

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

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

add-book-response.json
{
  "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: ...
Last updated on July 27, 2022