{"id":463,"date":"2022-04-06T16:14:00","date_gmt":"2022-04-06T15:14:00","guid":{"rendered":"https:\/\/dev.warpnet.nl\/?p=463"},"modified":"2025-12-08T13:29:28","modified_gmt":"2025-12-08T12:29:28","slug":"how-to-automatically-generate-clients-for-your-rest-api","status":"publish","type":"post","link":"https:\/\/warpnet.nl\/en\/blog\/how-to-automatically-generate-clients-for-your-rest-api\/","title":{"rendered":"How to Automatically Generate Clients for your REST API"},"content":{"rendered":"<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"538\" src=\"https:\/\/warpnet.nl\/wp-content\/uploads\/2022\/11\/openapi-cover-1024x538.png\" alt=\"\" class=\"wp-image-465\" srcset=\"https:\/\/warpnet.nl\/wp-content\/uploads\/2022\/11\/openapi-cover-1024x538.png 1024w, https:\/\/warpnet.nl\/wp-content\/uploads\/2022\/11\/openapi-cover-300x158.png 300w, https:\/\/warpnet.nl\/wp-content\/uploads\/2022\/11\/openapi-cover-768x403.png 768w, https:\/\/warpnet.nl\/wp-content\/uploads\/2022\/11\/openapi-cover-18x9.png 18w, https:\/\/warpnet.nl\/wp-content\/uploads\/2022\/11\/openapi-cover.png 1200w\" sizes=\"(max-width: 1024px) 100vw, 1024px\"\/><\/figure>\n\n\n\n<p>In this post we will automatically generate a<a href=\"https:\/\/en.wikipedia.org\/wiki\/Representational_state_transfer\" target=\"_blank\" rel=\"noreferrer noopener\">REST API<\/a>client based upon an<a href=\"https:\/\/swagger.io\/specification\/\" target=\"_blank\" rel=\"noreferrer noopener\">OpenAPI specification<\/a>. The OpenAPI specification allows REST APIs to be described in a standard programming<a href=\"https:\/\/en.wikipedia.org\/wiki\/Language-agnostic\" target=\"_blank\" rel=\"noreferrer noopener\">language-agnostic<\/a>way, making it possible for both humans and computers to interpret the specification.<\/p>\n\n\n\n<p>While working on some code to retrieve additional information (<a rel=\"noreferrer noopener\" href=\"https:\/\/github.com\/bunq\/sdk_python\/issues\/148\" target=\"_blank\">bunq\/sdk_python#148<\/a>) from the<a rel=\"noreferrer noopener\" href=\"https:\/\/github.com\/bunq\/sdk_python\" target=\"_blank\">bunq Python SDK<\/a>, we noticed that this SDK was automatically generated. We ended up monkey patching it, as we couldn't make a pull request to the SDK and the API specification or SDK generator wasn't publicly available. This aroused our interest about the automatic generation of API clients.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Write the OpenAPI specification<\/h2>\n\n\n\n<p>Even though quite a lot of REST APIs nowadays come included with an OpenAPI specification, not all of them do. In this post we will be using the JSON interface on<a rel=\"noreferrer noopener\" href=\"https:\/\/xkcd.com\/\" target=\"_blank\">xkcd<\/a>as an example of the latter. We will start by describing the OpenAPI specification for two existing endpoints:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>GET: http:\/\/xkcd.com\/info.0.json (current comic)\nGET: http:\/\/xkcd.com\/614\/info.0.json (comic #614)\n<\/code><\/pre>\n\n\n\n<p>Let's start by creating a file called<code>openapi.yaml<\/code>. After filling in some basic information such as the API information and available server(s), we can add the individual endpoints. The specification below contains the endpoint for retrieving an xkcd comic by its identifier.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>openapi: 3.0.0\n\ninfo:\n  version: 1.0.0\n  title: xkcd\n  description: 'A webcomic of romance, sarcasm, math, and language.'\n\nservers:\n  - url: https:\/\/xkcd.com\/\n    description: Official xkcd JSON interface\n\npaths:\n  # Retrieve a comic by its identifier\n  \/{id}\/info.0.json:\n    get:\n      tags:\n        - comic\n      description: Returns comic based on ID\n      summary: Find comic by ID\n      operationId: getComicById\n      parameters:\n        - name: id\n          in: path\n          required: <strong>true<\/strong>\n          schema:\n            type: integer\n      responses:\n        '200':\n          description: successfully returned a comic\n          content:\n            application\/json:\n              schema:\n                type: object\n                properties:\n                  link:\n                    type: string\n                  img:\n                    type: string\n                  title:\n                    type: string<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Automatically generate a Python SDK<\/h2>\n\n\n\n<p>The<a href=\"https:\/\/github.com\/OpenAPITools\/openapi-generator\" target=\"_blank\" rel=\"noreferrer noopener\">OpenAPI Generator<\/a>allows the generation of API client libraries and documentation for multiple languages and frameworks given an OpenAPI specification. This includes generators for<em>Python<\/em>,<em>Go<\/em>,<em>Java<\/em>and many more.<\/p>\n\n\n\n<p>Even though this generator is written in Java, you can use the<a href=\"https:\/\/hub.docker.com\/r\/openapitools\/openapi-generator-cli\" target=\"_blank\" rel=\"noopener\">pre-built Docker image<\/a>to act as a stand-alone executable. The command below will generate a Python SDK based upon the specification (<code>openapi.yaml<\/code>):<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$ docker run --rm.\n  -v ${pwd}:\/local openapitools\/openapi-generator-cli generate \u00c6rm\n  -i \/local\/openapi.yaml \u25ba\u25ba.\n  -g python\n  -o \/local\/out\/python }:\/local\/openapi.yaml\n  --additional-properties=packageName=xkcd_client,projectName=xkcd_client<\/code><\/pre>\n\n\n\n<p>After execution, the newly generated Python SDK is available in<code>.\/out\/python<\/code>and comes included with the necessary documentation to start using it. It even comes with a<code>.openapi-generator-ignore<\/code>file which lets you specify files to prevent them from being overwritten by the generator.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Improve the OpenAPI specification<\/h2>\n\n\n\n<p>Even though we can now use the newly generated SDK, it comes with some very generic class names and ill-favored function names, e.g.:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import xkcd_client\n\n\n# Initialize an xkcd API client\nconfiguration = xkcd_client.Configuration(host=\"https:\/\/xkcd.com\")\nclient = xkcd_client.ApiClient(configuration)\napi_instance = xkcd_client.api.default_api.DefaultApi(api_client)\n\n# Retrieve a comic by identifier\napi_response = api_instance.id_info0_json_get(614)<\/code><\/pre>\n\n\n\n<p>Let's improve the OpenAPI specificationto allow the generation of more human friendly code. To rename the<code>DefaultApi<\/code>class we will need to logically group endpoints by adding the<code>tags<\/code>option:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>...\npaths:\n  # Retrieve the current comic\n  \/{id}\/info.0.json:\n    get:\n      # A list of tags to logical group operations by resources and any other\n      # qualifier.\n      tags:\n        - comic\n      description: Returns comic based on ID\n...<\/code><\/pre>\n\n\n\n<p>In order to rename the function<code>DefaultApi.id_info0_json_get<\/code>we can specify a unique<code>operationId<\/code>to allow tools and libraries to uniquely identify an operation:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>...\npaths:\n  # Retrieve the current comic\n  \/{id}\/info.0.json:\n    get:\n      # A list of tags to logical group operations by resources and any other\n      # qualifier.\n      tags:\n        - comic\n      description: Returns comic based on ID\n      # Unique identifier for the operation, tools and libraries may use the\n      # operationId to uniquely identify an operation.\n      operationId: getComic\n...\n<\/code><\/pre>\n\n\n\n<p>The full OpenAPI specification of the xkcd JSON interface is available on<a rel=\"noreferrer noopener\" href=\"https:\/\/gist.github.com\/roaldnefs\/053e505b2b7a807290908fe9aa3e1f00\" target=\"_blank\">GitHub<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Using the generated Python SDK<\/h2>\n\n\n\n<p>After generating the Python SDK we can create a Python virtual environment and install the generated[xkcd Python API client using the following commands:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$ python3 -m venv env\n$ source env\/bin\/activate\n$ pip install -e .\/out\/python\n<\/code><\/pre>\n\n\n\n<p>After installing the generated API client, we could, as an example, retrieve the<a rel=\"noreferrer noopener\" href=\"https:\/\/xkcd.com\/1481\/\" target=\"_blank\">xkcd comic titled \"API\"<\/a>using the following Python snippet:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>from pprint import pprint\n\nfrom xkcd_client import Configuration, ApiClient, ApiException\nfrom xkcd_client.api.comic_api import ComicApi\n\n\nconfiguration = Configuration(host=\"https:\/\/xkcd.com\")\n\n# Enter a context with an instance of the API client\nwith ApiClient(configuration) as client:\n    # Create an instance of the API class\n    instance = ComicApi(client)\n\n    try:\n        # Retrieve the API comic\n        api_response = instance.get_comic_by_id(1481)\n        pprint(api_response)\n    except ApiException as exc:\n        print(\"Exception when calling ComicApi-&gt;get_comic_by_id: {0}\".format(exc))<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Conclusion<\/h2>\n\n\n\n<p>In general, it's quite possible to automatically generate a REST API based upon the OpenAPI specification. Even for REST APIs that do not include an OpenAPI specification by default you can easily describe the API in a specification file to allow the API client generation.<\/p>\n\n\n\n<p>Besides creating multiple API clients in different languages, the biggest benefits are shown when updating the clients as soon as the API specification has been changed. By simply calling the OpenAPI generator again using the updated specifications, you can update all your API clients with little effort. Even though the generated code may seem a bit rough, it's well documented and could be made more human friendly by improving the specification.<\/p>\n\n\n\n<p>Although this post doesn't go in depth on authentication, cookie usage or other HTTP methods, they are all usable features of the OpenAPI specification and OpenAPI Generator. Results may differ based upon the language or framework you would like to generate your SDK in, we're interested to hear about your experience using SDK generators.<\/p>","protected":false},"excerpt":{"rendered":"<p>In this post we will automatically generate a REST API client based upon an OpenAPI specification . The OpenAPI specification allows REST APIs to...<\/p>","protected":false},"author":4,"featured_media":467,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"_et_pb_use_builder":"","_et_pb_old_content":"","_et_gb_content_width":"","content-type":"","footnotes":""},"categories":[14],"tags":[],"class_list":["post-463","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-blog"],"acf":[],"_links":{"self":[{"href":"https:\/\/warpnet.nl\/en\/wp-json\/wp\/v2\/posts\/463","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/warpnet.nl\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/warpnet.nl\/en\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/warpnet.nl\/en\/wp-json\/wp\/v2\/users\/4"}],"replies":[{"embeddable":true,"href":"https:\/\/warpnet.nl\/en\/wp-json\/wp\/v2\/comments?post=463"}],"version-history":[{"count":2,"href":"https:\/\/warpnet.nl\/en\/wp-json\/wp\/v2\/posts\/463\/revisions"}],"predecessor-version":[{"id":1723,"href":"https:\/\/warpnet.nl\/en\/wp-json\/wp\/v2\/posts\/463\/revisions\/1723"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/warpnet.nl\/en\/wp-json\/wp\/v2\/media\/467"}],"wp:attachment":[{"href":"https:\/\/warpnet.nl\/en\/wp-json\/wp\/v2\/media?parent=463"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/warpnet.nl\/en\/wp-json\/wp\/v2\/categories?post=463"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/warpnet.nl\/en\/wp-json\/wp\/v2\/tags?post=463"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}