{"id":43284480,"url":"https://github.com/freshcells/graphql-rest-converter","last_synced_at":"2026-02-01T17:35:00.114Z","repository":{"id":57747642,"uuid":"501199713","full_name":"freshcells/graphql-rest-converter","owner":"freshcells","description":"Create OpenAPI-compatible HTTP APIs based on annotated GraphQL operations","archived":false,"fork":false,"pushed_at":"2024-07-08T10:22:12.000Z","size":1375,"stargazers_count":11,"open_issues_count":6,"forks_count":2,"subscribers_count":6,"default_branch":"main","last_synced_at":"2025-10-31T02:50:19.898Z","etag":null,"topics":["graphql","openapi","rest"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/freshcells.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-06-08T10:18:32.000Z","updated_at":"2025-09-19T00:22:12.000Z","dependencies_parsed_at":"2024-05-06T11:55:52.151Z","dependency_job_id":"c1512dfc-0600-4b1c-bad9-0d5deedf98a8","html_url":"https://github.com/freshcells/graphql-rest-converter","commit_stats":{"total_commits":67,"total_committers":3,"mean_commits":"22.333333333333332","dds":0.4477611940298507,"last_synced_commit":"ae659ff0b93215e14c8318216e50e6ea1f9b185a"},"previous_names":[],"tags_count":58,"template":false,"template_full_name":null,"purl":"pkg:github/freshcells/graphql-rest-converter","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/freshcells%2Fgraphql-rest-converter","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/freshcells%2Fgraphql-rest-converter/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/freshcells%2Fgraphql-rest-converter/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/freshcells%2Fgraphql-rest-converter/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/freshcells","download_url":"https://codeload.github.com/freshcells/graphql-rest-converter/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/freshcells%2Fgraphql-rest-converter/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28984420,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-01T16:29:42.054Z","status":"ssl_error","status_checked_at":"2026-02-01T16:29:41.428Z","response_time":56,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["graphql","openapi","rest"],"created_at":"2026-02-01T17:34:59.568Z","updated_at":"2026-02-01T17:35:00.107Z","avatar_url":"https://github.com/freshcells.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"## graphql-rest-converter\n\n[![Test](https://github.com/freshcells/graphql-rest-converter/actions/workflows/test.yaml/badge.svg)](https://github.com/freshcells/graphql-rest-converter/actions/workflows/test.yaml)\n[![codecov](https://codecov.io/gh/freshcells/graphql-rest-converter/branch/main/graph/badge.svg?token=C18QYCC7OA)](https://codecov.io/gh/freshcells/graphql-rest-converter)\n[![npm](https://img.shields.io/npm/v/@freshcells/graphql-rest-converter)](https://www.npmjs.com/package/@freshcells/graphql-rest-converter)\n[![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release)\n\n## Purpose\n\nThe package creates an HTTP API and a corresponding OpenAPI schema based on annotated GraphQL operations.\n\n## Features\n\n- Automatically generated request handlers\n- Automatically generated OpenAPI schema\n- Arbitrary GraphQL queries supported\n- Accurate OpenAPI type schemas for response and parameters derived from GraphQL schema\n- Support OpenAPI 3.0 for maximum compatibility with the OpenAPI ecosystem\n- Supports `application/x-www-form-urlencoded` requests\n- Supports `multipart/form-data` requests with file uploads as specified in\n  the [graphql-multipart-request-spec](https://github.com/jaydenseric/graphql-multipart-request-spec) specification.\n\n## Installation\n\nWith `npm`:\n\n    npm install @freshcells/graphql-rest-converter\n\nWith `yarn`:\n\n    yarn add @freshcells/graphql-rest-converter\n\n## [API documentation](https://freshcells.github.io/graphql-rest-converter/)\n\n## Supported OpenAPI versions\n\nThe package generates an OpenAPI schema for version 3.0 of the specification.\n\n## How to use\n\n### General work flow\n\nTo define a HTTP API request:\n\n1. Define a GraphQL operation to get the data from your GraphQL API\n2. Use directives on the operation to define URL path, parameter mapping, additional OpenAPI documentation, etc.\n3. The package will generate a request handler and an OpenAPI schema for the request\n\n### Entry point\n\nThe entry point is\nthe [`createOpenAPIGraphQLBridge`](https://freshcells.github.io/graphql-rest-converter/modules.html#createOpenAPIGraphQLBridge)\nfunction.\n\nThe function takes a config with:\n\n- The GraphQL document containing the operations with directives to define the HTTP API requests\n- The GraphQL schema of the target GraphQL API\n- A function to map custom scalars used in the target GraphQL API to OpenAPI type schemas (optional)\n\nThe function returns an object with two functions:\n\n- The\n  function [`getExpressMiddleware`](https://freshcells.github.io/graphql-rest-converter/modules.html#createOpenAPIGraphQLBridge)\n  is used to get the request handlers for the HTTP API as an `express` middleware\n- The\n  function [`getOpenAPISchema`](https://freshcells.github.io/graphql-rest-converter/modules.html#createOpenAPIGraphQLBridge)\n  is used to the get the OpenAPI schema for the HTTP API\n\n### Annotating GraphQL operations\n\n#### `OAOperation`\n\nThe `OAOperation` GraphQL directive is required on an GraphQL operation to map it to an HTTP request.\n\nThe arguments of the directive are described by the TS type:\n\n```typescript\ninterface OAOperation {\n  path: string\n  tags?: [string]\n  summary?: string\n  description?: string\n  externalDocs?: {\n    url: string\n    description?: string\n  }\n  deprecated: boolean\n  method: HttpMethod // GET, DELETE, POST, PUT\n}\n```\n\nThe `path` argument is required and defines the URL path of the resulting request.\nPath parameters can be defined with OpenAPI syntax, for example: `/my/api/user/{id}`.\n\nThe other arguments are mapped directly to the resulting OpenAPI schema for the request.\n\n#### `OAParam`\n\nThe `OAParam` is optional, every operation's variable declaration results in an API parameter.\n\nIf the path defined in the operation's `OAOperation` directive contains a parameter matching the variable name, the\nvariable will be mapped from the path parameter.\nOtherwise, the variable will be mapped from a `query` or `header` parameter.\n\nThe arguments of the directive are described by the TS type:\n\n```typescript\ninterface OAParam {\n  in?: 'path' | 'query' | 'header'\n  name?: string\n  description?: string\n  deprecated?: boolean\n}\n```\n\nThe `in` argument can be used to change the type of parameter.\nIt is useful, for example, if a variable should be mapped from a `header` instead of a `query` parameter.\n\nThe `name` argument can be used to explicitly define the parameter name. If it is not provided it uses the variable\nname.\nIt is useful, for example, if the desired parameter name is not a valid GraphQL variable name.\n\nThe other arguments are mapped directly to the resulting OpenAPI schema for the parameter.\n\n#### `OABody`\n\nLets you mark a query argument to be extracted from the request `body`.\nMainly designed for `input` types in combination with a `mutation`.\n\n```typescript\ninterface OABody {\n  description?: string\n  path?: string\n  contentType?: 'JSON' | 'FORM_DATA' | 'MULTIPART_FORM_DATA'\n}\n```\n\nYou can have multiple arguments annotated with `OABody`, the variable name (or the `path` overwrite) will then be\nexpected as `key` in the request\nbody. If you annotate only a single `InputType`, which is an object, there is no additional hierarchy introduced,\nthe `InputType` object is expected in the root.\n\n#### `OADescription`\n\nYou may optionally provide / override descriptions for `fragment` and `field` definitions.\n\n```typescript\ninterface OADescription {\n  description: string\n}\n```\n\n### Generating the request handlers\n\nTo generate the request handlers the\nfunction [`getExpressMiddleware`](https://freshcells.github.io/graphql-rest-converter/modules.html#createOpenAPIGraphQLBridge)\nis used.\n\nAs the first argument it takes a `GraphQLExecutor` implementation:\n\n- The package already provides two implementations:\n  - [`createHTTPExecutor`](https://freshcells.github.io/graphql-rest-converter/modules.html#createHttpExecutor)\n    - Takes the same arguments [`GraphQLClient`](https://github.com/prisma-labs/graphql-request#usage) from\n      the `graphql-request` package (mainly a URL)\n    - Creates an executor that resolves GraphQL operations via HTTP\n  - [`createSchemaExecutor`](https://freshcells.github.io/graphql-rest-converter/modules.html#createSchemaExecutor)\n    - Takes a [`GraphQLSchema`](https://graphql.org/graphql-js/type/#graphqlschema) from the `graphql` package as\n      argument\n    - Creates an executor that resolves GraphQL operations via the provided in-process GraphQL schema\n- Custom implementations can be created\n\nAs a second optional argument it takes a configuration object with the properties:\n\n- `responseTransformer`\n  - See [Configuration option `responseTransformer`](#configuration-option-responsetransformer)\n- `validateRequest`\n  - Validate HTTP requests against the generated OpenAPI schema\n  - Defaults to `true`\n- `validateResponse`\n  - Validate HTTP responses against generated OpenAPI schema\n  - Defaults to `false`\n\n#### Configuration option `responseTransformer`\n\nSometimes it may be necessary to adjust the HTTP response to achieve some required API behavior.\n\nFor example:\n\n- The structure of a GraphQL operation may need to be customized\n- Some values within a GraphQL operation result may need to be mapped or differently encoded\n- Some GraphQL operation results should be mapped to HTTP error codes\n- Another encoding for the response body than JSON is required\n\nIn general, when making use of this feature the OpenAPI schema needs to be adjusted accordingly.\n\nIf a response is transformed it will not be validated even if `validateResponse` is configured.\n\nIf used, a function should be provided. See the API documentation for the type of the\nfunction: https://freshcells.github.io/graphql-rest-converter/modules.html#ResponseTransformer.\n\nIt will be called for every request to the HTTP API with:\n\n- The OpenAPI schema details of the request\n- The GraphQL request details\n- The GraphQL response details\n\nIt should return:\n\n- `undefined` if the response should not be customized\n- HTTP response details in case the response should be customized\n\n### Generating the OpenAPI schema\n\nTo generate the OpenAPI schema the\nfunction [`getOpenAPISchema`](https://freshcells.github.io/graphql-rest-converter/modules.html#createOpenAPIGraphQLBridge)\nis used.\n\nAs an argument it takes a configuration object with the properties:\n\n- `baseSchema`\n  - As a convenience, this object will be recursively merged into the generated OpenAPI schema\n- `validate`\n  - Validates that the returned OpenAPI schema is valid according to the OpenAPI specification\n  - Defaults to `false`\n\n### Custom OpenAPI properties\n\nThe generated OpenAPI schema contains the customer properties `x-graphql-operation` and `x-graphql-variable-name`.\n\nThese custom properties contain all necessary information to generate the request handlers.\n\nTo remove the custom properties from the OpenAPI schema, for example before serving it publicly, the\ntransformation\nfunction [`removeCustomProperties`](https://freshcells.github.io/graphql-rest-converter/functions/removeCustomProperties.html)\ncan be used.\n\n## Usage example\n\nThis example showcases most of the usage discussed so far.\nThe hypothetical Star Wars GraphQL schema is inspired by the official GraphQL introduction: https://graphql.org/learn/.\n\n```ts\nimport express from 'express'\nimport fetch from 'node-fetch'\nimport { buildClientSchema, getIntrospectionQuery } from 'graphql'\nimport { gql } from 'graphql-tag'\nimport {\n  removeCustomProperties,\n  transform,\n  createHttpExecutor,\n} from '@freshcells/graphql-rest-converter'\nimport { createOpenAPIGraphQLBridge } from '@freshcells/graphql-rest-converter/express'\n\nconst GRAPHQL_ENDPOINT = 'https://example.org/graphql'\n\nconst BRIDGE_DOCUMENT = gql`\n  query getHeroByEpisode(\n    $episode: String!\n    $includeAppearsIn: Boolean! = false\n      @OAParam(\n        in: QUERY\n        name: \"include_appears_in\"\n        description: \"Include all episodes the hero appears in\"\n        deprecated: false\n      )\n  )\n  @OAOperation(\n    path: \"/hero/{episode}\"\n    tags: [\"Star Wars\", \"Hero\"]\n    summary: \"Retrieve heros\"\n    description: \"Retrieve heros by episode, optionally including the episodes they appear in\"\n    externalDocs: {\n      url: \"https://www.google.com/search?q=star+wars\"\n      description: \"More information\"\n    }\n    deprecated: false\n  ) {\n    hero(episode: $episode) {\n      name\n      appearsIn @include(if: $includeAppearsIn)\n    }\n  }\n\n  mutation createANewHero($name: String, $hero: HeroInput! @OABody(description: \"Our new Hero\"))\n  @OAOperation(path: \"/hero/{name}\", tags: [\"Star Wars\", \"Hero\"], method: POST) {\n    createNewHero(name: $name, input: $hero) {\n      name\n    }\n  }\n`\n\nconst LOCAL_PORT = '3000'\nconst API_PATH = '/star-wars'\n\nconst BASE_OPENAPI_SCHEMA = {\n  openapi: '3.0.3',\n  info: {\n    title: 'Star Wars API',\n    description: '...',\n    version: '1.0.0',\n  },\n  servers: [\n    {\n      url: API_PATH,\n      description: 'Local server',\n    },\n  ],\n}\n\nconst getCustomScalars = (scalarTypeName) =\u003e {\n  return {\n    DateTime: {\n      type: 'string',\n      format: 'date-time',\n    },\n    JSON: {},\n  }[scalarTypeName]\n}\n\nasync function main() {\n  const app = express()\n\n  const graphqlSchema = buildClientSchema(\n    (\n      await (\n        await fetch(GRAPHQL_ENDPOINT, {\n          method: 'POST',\n          headers: {\n            'Content-Type': 'application/json',\n          },\n          body: JSON.stringify({ query: getIntrospectionQuery() }),\n        })\n      ).json()\n    ).data,\n  )\n\n  const openAPIGraphQLBridge = createOpenAPIGraphQLBridge({\n    graphqlSchema,\n    graphqlDocument: BRIDGE_DOCUMENT,\n    customScalars: getCustomScalars,\n  })\n\n  const openAPISchema = openAPIGraphQLBridge.getOpenAPISchema({\n    baseSchema: BASE_OPENAPI_SCHEMA,\n    validate: true, // Default is false\n    // `removeCustomProperties` can be omitted if the underlying GraphQL operations should be visible as custom properties\n    transform: removeCustomProperties,\n    // or multiple transformers:\n    // transform: transform(removeCustomProperties, yourOwnTransformer)\n  })\n\n  const httpExecutor = createHttpExecutor(GRAPHQL_ENDPOINT)\n\n  const apiMiddleware = openAPIGraphQLBridge.getExpressMiddleware(httpExecutor, {\n    validateRequest: true, // Default is true\n    validateResponse: true, // Default is false\n    // Optional, can be used for customized status codes for example\n    responseTransformer: async ({ result, openAPISchema: { operation } }) =\u003e {\n      if (\n        operation?.operationId === 'getHeroByEpisode' \u0026\u0026\n        result?.status === 200 \u0026\u0026\n        !result?.data?.hero?.length\n      ) {\n        return {\n          statusCode: 404,\n          contentType: 'application/json',\n          data: JSON.stringify({ error: 'No heros found' }),\n        }\n      }\n    },\n  })\n\n  app.use(API_PATH, apiMiddleware)\n\n  app.get('/openapi.json', (req, res) =\u003e {\n    res.json(openAPISchema)\n  })\n\n  app.listen(LOCAL_PORT)\n}\n\nmain()\n```\n\n## Custom server\n\nThe library provides out of the box support for `express`. You may provide your own server with\n\n```ts\nimport { createRequestHandler } from '@freshcells/graphql-rest-converter'\n\n// ...\n```\n\nPlease consult the [express implementation](./src/express.ts) for an example.\n\n## GraphQL Upload extension\n\nBased on https://github.com/jaydenseric/graphql-upload\n\n### `Uploads` instead of `[Upload!]!`\n\nThe library comes with an extended version of the `graphql-upload` spec.\nIt introduces a new Scalar, `Uploads`, which allows to upload an arbitrary amount of files, similar to `[Upload!]!`.\n\nTo not depend on a known set of files (\nsee https://github.com/jaydenseric/graphql-multipart-request-spec?tab=readme-ov-file#file-list) beforehand (as this\nwould be uncomfortable to use in a REST API) we define that a field is an array, by appending `[]`.\n\nThe payload now looks like the following:\n\n```text\n--------------------------ec62457de6331cad\nContent-Disposition: form-data; name=\"operations\"\n\n{ \"query\": \"mutation($files: Uploads!) { multipleUpload(files: $files) { id } }\", \"variables\": { \"files\": null } }\n--------------------------ec62457de6331cad\nContent-Disposition: form-data; name=\"map\"\n\n{ \"files[]\": [\"variables.files\"] }\n--------------------------ec62457de6331cad\nContent-Disposition: form-data; name=\"files\"; filename=\"b.txt\"\nContent-Type: text/plain\n\nBravo file content.\n\n--------------------------ec62457de6331cad\nContent-Disposition: form-data; name=\"files\"; filename=\"c.txt\"\nContent-Type: text/plain\n\nCharlie file content.\n\n--------------------------ec62457de6331cad--\n\n```\n\nInstead of receiving a `Array\u003cPromise\u003cFileUpload\u003e\u003e` you will now receive an `AsyncGenerator\u003cFileUpload\u003e` that you can\niterate over.\n\n```typescript\n// ...\n\nfor await (const file of otherFiles) {\n  await yourUploadMethod(file.createReadStream())\n}\n```\n\n### Optional `Upload`\n\nYou may also now define optional `Upload` arguments. You will receive a `Promise\u003cFileUpload | null\u003e` in your resolver when\nsend via `multipart/form-data`.\n\n**Important**: Make sure to add `CSRF` protection when you allow uploading files with the graphql-upload extension.\n\n### Usage with express\n\n```ts\nimport { graphqlUploadExpress } from '@freshcells/graphql-rest-converter/express'\n\n// ...\n\napp.use('/graphql', graphqlUploadExpress())\n```\n\n## Upcoming features\n\n- OpenAPI 3.1 support\n- Direct support for more HTTP servers than express\n- Support for cookie parameters\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffreshcells%2Fgraphql-rest-converter","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffreshcells%2Fgraphql-rest-converter","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffreshcells%2Fgraphql-rest-converter/lists"}