smartapi-oasgraph

Generates a GraphQL schema for a given OpenAPI Specification (OAS)

Usage no npm install needed!

<script type="module">
  import smartapiOasgraph from 'https://cdn.skypack.dev/smartapi-oasgraph';
</script>

README

Travis (.org) npm Join the chat at https://gitter.im/oasgraph/Lobby

OASGraph

Generates a GraphQL schema for a given OpenAPI Specification (OAS).

Overview of translation

Note: To use OASGraph via the command line, refer to the oasgraph-cli package.

Installation

OASGraph can be installed using:

npm i oasgraph

Note that GraphQL.js is a peer dependency of OASGraph and must be installed separately (e.g., using npm i graphql).

Usage

The basic way to use OASGraph is to pass an OpenAPI Specification (OAS; version 2.0 or 3.0.x) to the generateGraphQLSchema function. The function returns a promise that resolves on an object containing the generated GraphQL schema as well as a report about possible issues when generating the schema:

const { createGraphQlSchema } = require('oasgraph')
// load or construct OAS (const oas = ...)
const { schema, report } = await createGraphQlSchema(oas)

OASGraph can also create GraphQL interfaces from multiple APIs. To do so, simply provide multiple OpenAPI Specifications.

const { schema, report } = await createGraphQlSchema([oas, oas2, oas3]])

Example of Serving the Generated GraphQL Schema

The schema generated by OASGraph can, for example, be served using Express.js and the GraphQL HTTP Server Middleware:

const express = require('express')
const graphqlHTTP = require('express-graphql')
const OASGraph = require('oasgraph')

async function main (oas) {
  // generate schema:
  const {schema, report} = await createGraphQLSchema(oas)

  // server schema:
  const app = express()
  app.use('/graphql', graphqlHTTP({
    schema,
    graphiql: true
  }))
  app.listen(3001)
}

main(oas) // oas loaded / constructed someplace else

Nested Objects

To create nested object types you need to define link objects in the OAS. According to the specification, a link object "represents a possible design-time link for a response." In other words, a link object describes how the data from one operation can be used to query another.

For example, let's say we have an API that has an operation called GET /users/{userId} and an operation called GET /employers/{employerId}. In addition, let's say that the user object returned from GET /users/{userId} contains a field called currentEmployerId. We can define a link object that says, use the currentEmployerId returned by GET /users/{userId} to query GET /employers/{employerId} in order to get the user's current employer. That link would look like the following:

{
  "operationId": "employer",
  "parameters": {
    "employerId": "$response.body#/currentEmployerId"
  }
}

If you define a link object, then OASGraph will add a new field to your object type. In this case, the User object type will have not only an currentEmployerId field, but also an employer field. Then, you will be able to create nested GraphQL queries like the following:

query {
  user(userId: "Alan") {
    currentEmployerId
    employer {
      name
    }
  }
}

To create nested object types for arrays, you will need to keep the following in mind.

Continuing from the previous example, let's say that there is a third operation called GET /friends/{userId} which would return an array of users, specifically the friends of a particular user. Furthermore, let's say you wanted to run the following query, which would allow you to get all the employers of Alan's friends:

query {
  friends(userId: "Alan") {
    currentEmployerId
    employer {
      name
    }
  }
}

If this was like the previous case, you would simply define a link from GET /friends/{userId} to GET /employers/{employerId}. However, this is impossible because of the current specification. This is because this operation returns an array rather than an object and the current specification does not provide a way to access individual elements of an array.

Nevertheless, OASGraph can still create a nested relationship. This is because OASGraph reuses object types. If GET /friends/{userId} returns an array of User object types, then each of those users will take on the links defined in other operations that return User object types. In other words, because GET /friends/{userId} returns an array of User object types and GET /users/{userId}, which also returns a User object type, has a link to GET /employers/{employerId}, you will still be able to get all the employers of a user's friends because of the shared type.


OASGraph can create GraphQL interfaces from multiple OASs. To create link between OASs, you will need use an operationRef instead of operationId. You will also need to create references using the title of the OAS. Although this is not supported by the specification, it is necessary for this functionality to work.

For example, let's say that there was a library API that would allow you to get a user's favorite books by querying GET /favoriteBooks/{name}. In addition, let's say that in the original API, the User object type contained two fields, firstName and lastName. To create a link between the original API and the library API, you would have to write something like the following:

{
  "operationRef": "I <3 Books API#/paths/~1favoriteBooks~1{name}/get",
  "parameters": {
    "name": "{$response.body#/firstName} {$response.body#/lastName}"
  }
}

Notice that the slashes in the path /favoriteBooks/{name} must be escaped with ~1 and that you can compose parameter values with different runtime expressions using brackets.

Options

The createGraphQlSchema function takes an optional options object as a second argument:

createGraphQLSchema(oas, [options])

The options object can contain the following properties:

  • strict (type: boolean, default: false): OASGraph generally tries to produce a working GraphQL schema for a given OAS if the strict mode is disabled. If OASGraph cannot fully translate a given OAS (e.g., because data schema definitions are incomplete or there are name collusions that cannot be resolved), createGraphQLSchema will per default degrade gracefully and produce a partial GraphQL schema. OASGraph will log warnings (given logging is enabled). If the strict mode is enabled, however, createGraphQLSchema will throw an error if it cannot create a GraphQL schema matching the given OAS perfectly.

  • headers (type: object, default: {}): Headers to be sent in every request to the API described by the given OAS. Parameters defined in the OpenAPI Specification to set these headers will be ignored by OASGraph.

  • qs (type: object, default: {}): Query parameters to be sent in every request to the API described by the given OAS. Parameters defined in the OpenAPI Specification to set these query parameters will be ignored by OASGraph.

  • viewer (type: boolean, default: true): The viewer object types (i.e. QueryViewer and MutationViewer) are artificial constructs that allow users to pass authentication credentials to OASGraph. Unfortunately, they are bulky and do not provide an accurate representation of the API. Depending on the API, it may be possible to send all credentials through the header option, so this option allows to disable OASGraph-generated viewer object types.

  • tokenJSONpath (type: string, default: undefined): Used to pass the JSONPath of the OAuth token in the GraphQL context. To see more details, click here.

  • addSubOperations (type: boolean, default: false): When true, OASGraph will nest GET operations based on their path hierarchy in the given OAS. E.g., when the OAS contains two paths /users/{id} and /users/{id}/friends, OASGraph will make friends queryable from within user. Note: This may cause problems when resolving GraphQL types in certain contexts, where the required variables are not available.

  • fillEmptyResponses (type: boolean, default: false): OASGraph, by default, will only wrap operations that have a response schema and operations that do not have a response schema will be ignored. The reason is that all GraphQL objects must have a data structure and in these cases, where the OAS does not define a response schema, the data structure cannot be safely assumed. As a result, it is recommended that the OAS should be modified to include a response schema. However, under certain circumstances, some operations should not in fact have a response schema. One circumstance is HTTP status code 204, in which no content should be returned. The option fillEmptyResponses will allow OASGraph to wrap these operations by assigning these operations a nullable data structure. Although this data structure is meaningless, the operation will appear in the schema.

  • baseUrl (type: string): Used to manual specify the base URL which all paths will be built on. Normally, OASGraph will select a base URL from the server object defined in the OAS. However, if the server object contains multiple URLs, OASGraph will randomly select one. The purpose of this option is to provide greater control over the base URL in these situations, especially when the OAS cannot be modified. This option may also prove to be useful in testing and development.

  • operationIdFieldNames (type: boolean, default: false): By default, query field names are based on the return type type name and mutation field names are based on the operationId, which may be generated if it does not exist. This option forces OASGraph to only create field names based on the operationId.

  • requestOptions (type: object, default: {}): Additional options, provided by the Request module, that can be used to configure the HTTP calls that powers the generated GraphQL resolvers. A common usecase is to use this to set a web proxy through the proxy field.

Consider this example of passing options:

OASGraph.createGraphQLSchema(oas, {
  headers: {
    authorization: 'asfl3032lkj2' // send authorization header in every request
    'x-origin': 'GraphQL' // send header to identify requests made via GraphQL
  },
  qs: {
    limit: 30 // send limit query string in every request
  },
  addSubOperations: false,
  requestOptions: {
    proxy: "http://my-proxy:3128"
  }
})

Authentication

Per default, OASGraph will wrap API requests that need authentication in corresponding viewers, which allow the user to pass required credentials. OASGraph currently supports viewers for basic authentication and API keys. For example, a query using an API key viewer is:

{
  viewerApiKey (apiKey: "api_key_here") {
    ...  // query for authenticated data here
  }
}

OASGraph uses dedicated viewers for mutations. For example, a mutation using a basic authentication viewer is:

mutation {
  mutationViewerBasic (username: "user", password: "secret") {
    ...  // mutate authenticated data here
  }
}

OASGraph further provides anyAuth viewers (for queries and mutations), which allow the user to simultaneously provide information for multiple authentication mechanisms. AnyAuth viewers allow OASGraph to resolve nested queries and mutations that encompass API requests with different authentication mechanisms. For example, consider the following query:

{
  viewerAnyAuth (
    exampleApiKeyProtocol: {apiKey: "a1p2i3k4e5y"}
    exampleBasicProtocol: {
      username: "erik"
      password: "secret"
    }
  ) {
    patentWithId (patentId: "test") {  // requires "exampleApiKeyProtocol"
      patentId
      inventor {                       // requires "exampleBasicProtocol"
        name
      }
    }
  }
}

Authorization

Because OASGraph is a library, it cannot make the callbacks that OAuth requires by itself. Instead, the user must take care of the callback. After the user has obtained the OAuth token from the callback, simply pass the token, specifically the path of the token, to OASGraph through the tokenJSONpath option.

To see an example of how this would work, click here!

Logging

OASGraph provides multiple levels of logging, which can be controlled by a DEBUG environment variable. You can enable these levels using:

DEBUG=level_1,level_2 node app-using-oasgraph.js

The following logging levels are supported:

  • preprocessing: Logs information about preprocessing the OAS.
  • translation: Logs information about translating an OAS to GraphQL.
  • http: Logs information about the HTTP requests made to the API.

Testing

To test OASGraph, run:

npm test

This command will temporarily start and later shut down an example REST(-like) API.

License

MIT