Create an SDK using OpenAPI specs

It’s not something a lot of people need to do on a regular basis, but when you do, you don’t want to spend a lot of time doing it. I’m referring to the process of “create an SDK for your APIs”.
When your service is exposing endpoints for your consumers to use, it’s easy to refer them to using raw endpoints and let them figure out how to deal with it based on the Open API specifications (formerly known as Swagger) provided. This requires quite a bit of plumbing on their part. Needless to say, invoking endpoints from your API by using HTTP-calls isn’t very user-friendly and quite error prone. Providing your consumers with a proper SDK is much better from an onboarding perspective.

How to start building an SDK?

As mentioned, either you or your customer needs to do quite a bit of plumbing to invoke your API endpoints properly. This isn’t something anyone will enjoy, so automation is everyones friend over here.

To start, you first need to make sure you are generating Open API specs for the API. Just about every ecosystem has generators for this process. In het .NET ecosystem we’ve been using Swashbuckle for a couple of years now, and with the release of .NET 9 there’s also out-of-the-box capabilities to generate the API specifications.
With these files generated, you’re already halfway of creating an SDK. For best results, be as complete as possible with your Open API specs. Provide descriptions, all error codes, use DTOs, adhere to the HTTP specifications, etc.

The next half of the SDK generation is using the OpenAPI Generator.

How to use the Open API Generator?

Both the Open API Generator site and the corresponding GitHub repository have quite good documentation on how to work with this tool and set it up.

At this time, what it comes down to install the following prerequisites & the tool itself.

Java

The following command will install a Java version on your system, as provided in the MS Learn docs.

winget install Microsoft.OpenJDK.21

In a new Terminal session, you should be able to run java --version to see which version got installed.

Maven

Maven can be downloaded from the official Apache Maven Project page. The Binary zip archive is good enough for this use-case.

Extract the contents of the package to a folder of your choosing. adding it to my C:\Tools\ folder. Once extracted, be sure to add the bin-folder, containing the mvn file, to your %PATH%

In a new Terminal session, you should be able to run mvn --version to see which version got installed.

OpenAPI Generator

The easiest way to work with the generator is by installing the JAR file to a location where your source code is located too.

Invoke-WebRequest -OutFile openapi-generator-cli.jar https://repo1.maven.org/maven2/org/openapitools/openapi-generator-cli/7.9.0/openapi-generator-cli-7.9.0.jar

You should be able to run the following command and get details on the generator.

java -jar openapi-generator-cli.jar help

The full list of commands available and how to use can be found in the Usage documentation of the OpenAPI Generator.

How to create an SDK from Open API specs?

With all the prerequisites handled you can generate an SDK for your API with just a one-liner:

java -jar openapi-generator-cli.jar generate `
    -g csharp `
    -i ./path-to-your-openapi-specs-file.yaml

This single command will create a C# SDK the API, along with documentation for all exposed methods, a test project with unit test placeholders, and an AppVeyor build pipeline. Very powerful and useful.

By just having this, you can make a much more streamlined experience of the customers using your product.

How to customize the created SDK?

The generated SDK that’s coming out of the box is good enough for usage already, but there are probably things you might want to customize. Especially, if you want to remove some of the plumbing required, like creating tokens to make an authenticated request. The OpenAPI Generator facilitates in this requirement by being able to extract all used templates and modify them to your liking. The templates are written with Mustache for just about every language available.

Get the default templates

To inspect the default templates, you can extract them from the earlier downloaded jar-file. Take for example the following two commands that export both the C# and Python files.

# For C#
java -jar openapi-generator-cli.jar author template -g csharp -o ./src/openapi3/templates/ootb/csharp

# For Python
java -jar openapi-generator-cli.jar author template -g python -o ./src/openapi3/templates/ootb/python

Both folders will contain a dozen of Mustache files used by the generator to create the SDK & documentation. If you want to customize one or more of these files, copy them to a separate folder and make your modifications. During generation, you can specify the modified templates.

Use a generation template

Working via the CLI works fine, but it’s not very maintanable in my opinion. Luckilly for us, the generator also provides the capability to use a configuration file (JSON).

Here’s a configuration file I’ve been using for an (unofficial) SDK of the Manufacturing solution I’m involved with.

{
    "useDateTimeOffset": false,
    "returnICollection": false,
    "validatable": false,
    "templateDir" : "./src/openapi3/templates/mds/csharp",
    "packageName" : "Microsoft.Industry.Manufacturing.Sdk",
    "outputDir" : "./src/generated/mds/csharp",
    "inputSpec" : "./src/openapi-specs/mds-service-100-preview.yaml", 
    "packageVersion" : "1.0.0-preview",
    "packageDescription" : "Unofficial .NET SDK for the Microsoft Cloud for Industries - Manufacturing data solutions in Microsoft Fabric",
    "packageTitle" : "Unofficial .NET SDK for the Manufacturing data solutions in Microsoft Fabric",
    "packageCompany" : "Microsoft",
    "packageAuthors" : "Microsoft",
    "packageTags" : "microsoft industry manufacturing api",
    "packageCopyright" : "Microsoft",
    "gitHost": "github.com",
    "gitUserId" : "jandev",
    "gitRepoId" : "sdk-from-openapi-specs", 
    "targetFramework" : "net8.0"
}

The full code can be found in my GitHub repository, used as a basis for this post: https://github.com/Jandev/sdk-from-openapi-specs

As you can see, this configuration file already prepares the SDK with the fields to create a package, like a NuGet package. All of these settings are documented.

With this file set up, you can refer to it during the generation step.

java -jar openapi-generator-cli.jar generate `
    -g csharp `
    -c ./src/openapi3/config-csharp-mds.json

The templateDir is especially important, as it’s pointing to the folder with your customized templates.

How to make customizations?

As I mentioned earlier, making customizations is as easy as copying the default files to a new folder and specify this folder during the generation phase.

In my GitHub repository I’ve made some modifications to the authentication part. The changes made are to make sure the SDK is handling the creation of a token itself, so a user will not have to bother with this. The user only needs to specify some configuration details, like the Application ID URI and Managed Identity Client Id.
You can see the modified README.mustache and configuration change in the template folder.

The method GetToken() in OAuthAuthenticator.mustache is handling all of the plumbing to retrieve a token for an authenticated call to the endpoints.

As you can see in the linked examples, you can modify both the code and documentation of the generated SDK. This makes the OpenAPI Generator a very powerful tool as the extensibility is very good and it’s using open industry standards.

When to create an SDK?

I would argue, every time you expose an API to another team or customers it is beneficial for both parties to have an SDK available.
As both the Open API specifications AND the SDK can be generated automatically, there is almost no overhead to add this in your release pipeline. Of course, the artifacts do need to be validated. I do think this is worth the extra effort up front, as it’ll refrain users from making mistakes when invoking endpoints and you’re in control on how they (by default) invoke the endpoints.


Share