Open API (Part 2): Handcrafted, bespoke, artisanal Open API specs: API contract-first

Tina HollyTina Holly
6 min read

Overview

This article is the 2nd in a series of blog posts that discuss using Open API to help with API documentation and API client code generation.

In the previous article, we went over how we can generate documentation and API clients using existing Open API spec files. In this article, we’ll go over one way we can create this spec file, which will be the foundation for documentation and code generation.

The next 2 articles (this one and the next) will go over the 2 separate approaches I’m covering for creating an Open API spec file:

  1. API contract-first, bespoke artisanal handcrafted Open API spec file

  2. Generated Open API spec file

Why (and why not) API contract-first?

One approach to creating Open API spec files is to create the API contract and definition first and then build to the spec.

This is a great approach if you have multiple teams working on an API and you are building towards a common goal.

This approach only works if all teams are on board to doing it this way. If, say, one team is building out the front-end to an API contract, but the backend team is not adhering to the defined contract, then the front-end will not work, and the documentation is essentially useless.

There are some pros with this approach:

  • API contract can be formalized up front, allowing back-end and front-end teams to respectively work in parallel on the software to support the back-end and front-end of the product

  • API design is up front and centre, enabling API designers to optimize for the developer experience of the future API consumers

There are some cons with this approach:

  • If you’re working in a setup where back-end and front-end teams do not collaborate, what can happen is that the front-end team builds towards an API contract they thought was finalized, meanwhile the backend team is building out an API that does not adhere to the contract. This would then require the front-end team to catch up on the API changes in order for their software to work, or the back-end team to revisit the API they implemented to respect the contract.

  • Hand-coding these spec files can be tedious and prone to error if you do not have the right tools at your disposal to assist in validating your API schema and testing the documented endpoints

  • Spec files are disconnected from the code and risk becoming out of date

In this blog post, covering the scenario where API contract changes are not adequately communicated is out of scope, but we will be going over the tools we can use to help make hand-coding Open API spec files an accurate and enjoyable process.

Optimizing for hand-coding nested JSON files

I will be blunt with this part and state that I prefer using the JSON format for Open API specs. I have not been a fan of YAML in general as I find it difficult to follow the indentation. I like my code braces.

In the below screenshot, I’ve got JSON and YML side-by-side for comparison.

In the above screenshot you may have noticed the rainbow-coloured JSON properties that change colours as the level of JSON is nested deeper. This is my custom Visual Studio Code theme called Tinacious Design. Since early 2020 I’ve had support for over 30 levels of JSON in my theme. This is probably excessive but I like it when working with Open API specs or other large, deeply nested JSON files.

I’m guessing there’s similar support for YML but I haven’t run into it.

If you’re using the Swagger Editor online, you may prefer YAML since they have better syntax highlighting support for it.

I prefer to use my own tools since I have access to the code in the same editor.

If you’re using your own tool, it would be a great idea to ensure you have the ability to preview your API. There are many extensions that have support for previews.

Another thing you’d definitely want is language support so that you can ensure you’re your spec is valid. There are also many extensions that claim to have language for Open API specs.

I ended up choosing one extension that did both. Please note that this appears to be an extension that is a very helpful suite of tools that wraps a commercial product. I did not use the paid features of this extension, only the free ones for editing and previewing my spec file.

The first screenshot above shows the OpenAPI preview with all elements collapsed using the Tinacious Design (dark) theme, while the second shows the OpenAPI preview with one of the elements expanded, using the Tinacious Design (High Contrast, Dark) theme. Both of those themes support colourizing for 30+ levels of nested JSON and are available in this extension.

Generating API types from the hand-coded Open API spec

There are many tools available for this task. After a quick Google search and looking over the documentation, I ended up going with openapicmd. While this was a good tool to generate all of the types into a single file, the API wasn’t readable enough for me to know at a quick glance how to use the same tool for implementing a generated client as discussed in the Part 1 blog post, so for that part I ended up choosing a different tool, heyapi/openapi-ts, which can do both in a straightforward way.

Here’s the command I used to generate the types from the spec file using openapicmd:

npx openapi typegen public/openapi.json > models/api-types/api-schema.d.ts

This outputs all the types to a single file.

Dogfooding the API types

One thing we can do to try to connect the API spec to the real code is using the types that end up being generated. For example, if you’re writing a Next.js or Express handler, you can do so in a way where you use the generated response types. Here’s a very naive example of that:

import { NextApiRequest, NextApiResponse } from 'next';
import { BaseResponse } from '../../utils/api/api-utils';
import { Environment } from '../../utils/Environment';
import { ResponseGetHealth } from '../../models/api-types/api-schema';

export default async function healthHandler(
  request: NextApiRequest,
  response: NextApiResponse<BaseResponse<ResponseGetHealth['data']>>
) {
  return response
    .status(200)
    .json({
      data: {
        version: Environment.getGitCommitHash() || 'unknown',
        openapi: 'https://tinaciousdesign.com/openapi.json'
      }
    })
}

It uses the generated type ResponseGetHealth:

export interface ResponseGetHealth {
  data: {
    /**
     * example:
     * 7ca91a975bfb85b940a290cd3116330ef9fbe6a9
     */
    version: string;
    /**
     * example:
     * https://tinaciousdesign.com/openapi.json
     */
    openapi: string;
  };
}

For example, if I delete the openapi property which is required by the spec, it’ll show an error:

Summary

This set of tools and approaches is one way we can create Open API specs to support generating API clients and documentation. This is a great approach for creating high-quality API documentation provided that all teams involved are willing to work towards creating and building against an agreed-upon API contract.

This is the 2nd post in a series of post. In the next one, we’ll be going over a different approach, which is code-first rather than API contract-first, in which documentation is generated from the code. Stay tuned for the next one!

10
Subscribe to my newsletter

Read articles from Tina Holly directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Tina Holly
Tina Holly

Hi, my name is Tina! I am a Full Stack Software Developer and Tech Lead from Canada. 🙋🏻‍♀️