OpenAPI specifications: how to make them


What is the OpenAPI Specification?

The OpenAPI Specification is a specification that describes a well-laid-out approach to defining the structure of a RESTful API. There are many ways you can describe the APIs you create. The OpenAPI specification makes it easy by defining a set of rules and providing guidelines that anyone can follow to document their APIs which can later be used to understand how an API works. This presents a whole host of opportunities especially the fact that the same specification can be fed to computers to generate many other things including documentation and even code snippets.


What is it good for?

The OpenAPI specification looks like a config file, in fact, you can draft them using either the JSON or YAML format which themselves dominate the config file space.

Because they can be interpreted by computers you can do a whole host of things, from generating code in any language that brings the API to live to generating SDK-like code samples that other developers can use to connect to your API. You can even generate interactive documentation as well.

Swagger the project under which the specification standard was created also provides tools to generate some of the things I mentioned earlier. In the next section, we are going to look at how to use one of such tools to create a sample OpenAPI specification.


How do you create one?

swaggerEdtior

Knowing YAML and having a code/text editor is half what you need to create an OpenAPI spec. One of the tools the swagger team provides is a browser-based editor called the Swagger Editor designed specifically for creating and editing the OpenAPI spec. It comes with an inbuilt interactive previewer, syntax highlighting, and error detection among other things.

For our tutorial, I have created a very basic CRUD API https://lit-taiga-02928.herokuapp.com which we will be creating the spec for. It's an API for storing and managing books and their page sizes. Also, find the complete specification for the above API here, copy-paste this into the Swagger editor to follow along with the tutorial.

OpenAPI Specification structure

You can write your spec using either JSON or YAML as I mentioned before, However, most specs are written in YAML because its a lot more readable compared to JSON. YAML's syntax is based on key/value pairs and uses indentation to denote information hierarchy as we will see in a bit.

The OpenAPI spec itself is broken up into many parts with each section representing a key part of our spec definition:

  • MetaData: This section holds information describing what our API does as well as information regarding the version of the OpenAPI standard we are using.
  • Server: This section holds information regarding the server that hosts our API, for example, the URL to access it.
  • Paths: This is the meat of our spec, it defines every part and option for each API a user can access.
  • Components: This section is reserved to hold reusable parts of our spec, for example, if two endpoints have the same input, we don't have to define it twice we can store it in the component section and use just reference it in both places.
  • Security: Every security element of our API from API keys to 0Auth is defined under the security section.

The above sections are by no means an exhaustive list, they however are the core sections needed to have a comprehensive spec.

Adding our metadata and server structures

01: openapi: 3.0.0
02: info:
03:   description: The books endpoint keeps a record of books and their page numbers 
04:   version: 1.0.0
05:   title: Book API
06: servers:
07:   - url: https://lit-taiga-02928.herokuapp.com
08:   - url: http://lit-taiga-02928.herokuapp.com

Now let's start by breaking down our pre-created spec The first line specifies the version of the OpenAPI we are working with. In this case version 3, followed by a section describing the API we are designing the spec for.

The second section servers: holds information regarding the server hosting our API.

Also, note how we indent some of the key/value pairs in each section. Parts of the YAML file are indented when they form part of a section and the end of the indentation marks the beginning of a new section.

So for our example above this will mean description, version, and title are part of the info: section, and servers: is a different section with url being a part of that.

The next step is to define our endpoints, and this is done under the path: key, you may have also noticed the swagger editor hinting at this in the form of an error.

Defining our endpoint and method structure

01: paths:
02:     /:
03:      get:
04:       summary: Get the list of all books
05:       description: This endpoint will return the list of all books
06:       # TODO: Add response structure
07:     post:
08:       summary: Add a new book
09:       description: Add a new book to the book archives
10:       # TODO: Add request and response structure
11:     "/{id}":
12:     put:
13:       summary: Update an existing book
14:       description: Update an existing book in the books archives
15:       # TODO: Add request and response structure 
16:     delete:
17:       summary: Deletes a book
18:       description: Delete a book from the list
19:       # TODO Add request and response structure

To denote the beginning of the endpoint definition section we add the paths: key, right after which we list all the sub URLs or endpoints in our case thus / and /{id}. The indentation of get and post under / implies that both request types use the same URL structure, likewise put and delete have the URL structure /{id} where id is a placeholder for the id of the book the user will like to update or delete respectively.

With our URL definitions and descriptions out of the way, the next step is to define the request structure for our APIs.

Defining the request structure

01: paths:
02:     /:
03:      get:
04:         # ...
05:     put:
06:       summary: ...
07:       parameters:
08:         - name: id
09:           in: path
10:           description: ID of the book you will like to update
11:           required: true
12:           schema:
13:             type: integer
14:             format: int64
15:       requestBody:
16:         content:
17:           application/json:
18:             schema:
19:               $ref: "#/components/schemas/Book"
20:         description: JSON payload of the book that needs to be updated
21:         required: true
22:       # TODO: response structure 
23: 
24: components:
25:   schemas:
26:     Book:
27:       type: object
28:       required:
29:         - title
30:         - pages
31:       properties:
32:         title:
33:           type: string
34:           example: Harry Potter
35:         pages:
36:           type: integer
37:           example: 123      

Besides the GET request all the other three requests will require some form of input or request data. For example in other to add a new book you will need to provide the new book as a request JSON body, same thing applies to the put request this will be set under the requestBody: key.

Also in the case of put and delete, the book you will like to either update or delete requires you to pass in the ID as a path parameter, which is defined under the parameter: section.

Note the line $ref: "#/components/schemas/Book" in the requestBody: this points to the components section which holds reusable parts of our specification. In this case, the book JSON structure is mocked and reused in different parts of our spec. Later we will see this also being used in our response sections.


Defining the response structure

Next let's write out the spec for our get response, this is meant to return the list of all our books in JSON format as shown below.

01: {
02:    "message":"List of books",
03:    "payload":[
04:       {
05:          "title":" In Search of Lost Time ",
06:          "pages":4215
07:       },
08:       {
09:          "title":" Ulysses",
10:          "pages":730
11:       },
12:       {
13:          "title":"The Great Gatsby",
14:          "pages":208
15:       },
16:       {
17:          "title":"Moby Dick",
18:          "pages":378
19:       }
20:    ]
21: }

Let's take the above JSON response and represent it in our spec as seen below in the response: section. You will also notice there are two sections 200 and 405, these are HTTP status codes, and anything under them describes what the user gets back depending on the response from the server.

The important section here is schema: which holds a YAML version of our JSON response above. This takes a bit of practice to learn to convert JSON to YAML and I will recommend taking some time to read on OpenAPI Data Types as this helps

01: responses:
02:     "200":
03:         description: successful operation
04:         content:
05:           application/json:
06:             schema:
07:             type: object
08:             properties:
09:                 message:
10:                   type: string
11:                   example: Book added successfully
12:                 payload:
13:                   type: array
14:                   items:
15:                       $ref: "#/components/schemas/Book"
16:     "405":
17:         description: Invalid input

Testing and debugging

Swagger UI walk through

During the creation of your spec using the Swagger Editor you get a lot of useful things such as the Editor letting you know if there is something wrong with your spec and on which line and sometimes a possible fix.

You are also able to test out your APIs to see if you captured them correctly in your spec. To do this you just need to expand the right endpoint in the interactive section to the right and click on the "Try it out" button, this will provide you with input boxes to fill out the needed request data and then generate a CURL based code snippet. You can then run this in your command line to see the results. If you get the right result then you know your spec is correct

Conclusion

For each part of our specification, there are many more options we could have added, we didn't use most of the constructs because I wanted to keep the tutorial simple. Depending on the structure of your API you might need to use some of these constructs, for example, auth. To know which constructs are available to you, you should refer to the OpenAPI documentation.

Also once you have an OpenAPI spec you can convert this into documentation for your end users using a tool like Redocly, I have another article covering this if you are interested.

Also if you enjoyed this article please consider subscribing to my newsletter to know whenever I post something new 😊.