Red Green Repeat Adventures of a Spec Driven Junkie

Swagger/OpenAPI from Ground Up

I love using the Swagger/OpenAPI tool to develop APIs as I get a broader context on how to prototype the whole API quickly. Seeing the interactive documentation as I prototype the API is fantastic feedback. I get a feel for the craft of a good API.

When I have developed APIs without Swagger/OpenAPI, I would move quickly on all the easy items (i.e. return all the users), but when it came down to returning a select set of users from a query parameter array list, I would get lazy because 1) I couldn’t figure out how to do it right and 2) wasn’t motivated enough to do it right. I can fix it later, right?

Sorry, with an API, once you define an API interface, it’s there for a long time. Getting the API design right from the start makes a big difference.

This time, I want to go over key Swagger/OpenAPI formats to craft your API in the Swagger/OpenAPI editor by building an API from the ground up. This way, I can show the primitives in a more raw form have a clean slate - just enough for what you need, and I get a cheatsheet on items I use.

If you don’t know it yet, this is a follow up from my Swagger Editor: My Favorite Features article.

Items I will cover:

  • minimum API description
  • referencing
  • defining arrays & enumerated values in the response and query parameter
  • using format such as date-time

Requirements

Basic User API

Let’s start with the absolute minimum API description in swagger:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
swagger: '2.0'
info:
  title: "minimum viable swagger editor"
  version: 0.0.1
  description: "the least amount of setup to get swagger editor going"

paths:
  /user:
    get:
      responses:
        200:
          description: "the user that runs the show on here"
          schema:
            type: 'object'
            properties:
              username:
                type: 'string'

This fulfills all the basic requirements for a swagger file:

  • swagger front matter
    • swagger version
    • info containing title, version, and description
  • single endpoint

This endpoint returns a single user’s username. Which one? It’s up to the implementation, but this shows how to query for that user and the response format of the query.

Adding Additional items

In the properties section, we can start adding new properties to return with the user such as: display_name. To add that, we would add:

            properties:
              username:
                type: 'string'
              display_name:
                type: 'string'

Return ALL the users

Let’s add another endpoint that returns all the users of the system in an array:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
  /users:
    get:
      responses:
        200:
          description: 'get all the users'
          schema:
            type: 'object'
            properties:
              counts:
                type: integer
              data:
                type: 'array'
                items:
                  type: 'object'
                  properties:
                    username:
                      type: 'string'
                    display_name:
                      type: 'string'

New items with this endpoint:

  • counts - this is the number of items returned in the data field
  • data - this contains user objects.

In swagger, the array syntax for data is basically:

type: array
  items:
    types: 'object'
    properties:
      ...

Only array that return object type require the properties field. The properties field do not need to exist. when the array contained types: boolean, integer, string, etc.

This is starting to look like a solid endpoint. /user returns a single user. /users returns all the users wrapped in a data field and include the counts.

Keep it DRY

What if I want to export another field in Users? In this case, I would have to update the properties field of /user and /users. In this case, two is not that hard to keep in sync, but managing the sync becomes a pain.

Referencing

Instead of writing the same properties description every place the same object appears anywhere in the file, use the Reference feature.

To use the reference feature, create a definitions section in the swagger file:

1
2
3
4
5
6
7
8
definitions:
  User2:
    type: object
    properties:
      name:
        type: string
      display_name:
        type: string

With this definition, any section can use this with syntax: $ref: '#/definitions/<reference name>

So, to use it, we can create an endpoint for User2 now:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
  /user2:
    get:
      responses:
        200:
          description: "another version using schema"
          schema:
            $ref: '#/definitions/User2'

  /user2s:
    get:
      responses:
        200:
          description: 'get all the user2s'
          schema:
            type: 'object'
            properties:
              counts:
                type: integer
              data:
                type: 'array'
                items:
                  $ref: '#/definitions/User2'

Now, changes that happen in the definitions section for User2 will appear immediately in the /user2 and /user2s endpoint.

Way to keep the swagger file DRY!

Model

Another bonus when using defnitions: the model section comes for free!

Scroll down to see the model definition for User2:

Swagger Model Section

This gives a comprehensive definition of the model of the object in a single location. (Perfect to start building out internal storage and/or representations of the model. ;-) )

Tip: to see changes when live editing, close and open the whole model section for updates.

Additional Types

Some helpful types to use when defining:

Enumerated values

If you want to list a field can only take on certain values, use an enumerated type:

      role:
        type: string
        enum:
        - user
        - admin

The type here specifies what kind of enumerated items can appear in the list and the enum field lists those as an array. In this case, the user2’s role can either be a ‘user’ or and ‘admin’.

Date-Time

To specify a field will be a date-time field instead of just ‘string’, use the format option with the string type:

      last_login:
        type: string
        format: date-time

Query Parameters

The next coolest place to start specifying is query parameters. The format is similar to the definition, but there’s additional set up required.

Integers

To specify an integer as a query parameter, this is the syntax:

     parameters:
        - name: limit
          in: query
          type: integer

Parameters can be in a query or the body. The type specifies allows different types, such as integer, boolean, string, etc.

Arrays

To specify query parameter to accept an array of items (such as a list of usernames), this is the structure:

        - name: usernames
          in: query
          type: array
          collectionFormat: csv
          items:
            type: string

The type is array, requires option: collectionFormat as well as the format, in this case: csv. There are others such as tab-seprated, space-separated, pipe-separated, etc. The items specify the expected type within the array.

Enum

To specify query parameter to accept an enumerated value, it is similar to basic query parameters:

        - name: role
          in: query
          type: string
          enum:
          - user
          - admin

The type is string and including an enum list of the items.

Conclusion

Swagger/OpenAPI provides a rapid prototyping tool to give a great top down view of an API’s details. With the interactive editor/documentation, it motivates me to craft the best API possible and do it right the first time.

Starting with a minimum API description, one can see how different Swagger/OpenAPI formatting works, such as referencing, array & enumerated value definition in the response & query parameter and how to specify the date-time format.

With just these items, this significatnly improves my API design. For your next API, start with Swagger/OpenAPI, you will make a beautiful API.

Appendix

The YAML file used in this article is below, copy into the swagger editor!

swagger: '2.0'
info:
  title: "minimum viable swagger editor"
  version: 0.0.1
  description: "the least amount of setup to get swagger editor going"

paths:
  /user:
    get:
      responses:
        200:
          description: "the user that runs the show on here"
          schema:
            type: 'object'
            properties:
              username:
                type: 'string'
              display_name:
                type: 'string'

####################
# Array of Objects #
####################
  /users:
    get:
      responses:
        200:
          description: 'get all the users'
          schema:
            type: 'object'
            properties:
              counts:
                type: integer
              data:
                type: 'array'
                items:
                  type: 'object'
                  properties:
                    username:
                      type: 'string'
                    display_name:
                      type: 'string'

###############
# Referencing #
###############
  /user2:
    get:
      responses:
        200:
          description: "another version using schema"
          schema:
            $ref: '#/definitions/User2'
  /user2s:
    get:
      responses:
        200:
          description: 'get all the user2s'
          schema:
            type: 'object'
            properties:
              counts:
                type: integer
              data:
                type: 'array'
                items:
                  $ref: '#/definitions/User2'

####################
# Query Parameters #
####################
      parameters:
        - name: limit
          in: query
          type: integer
        - name: names
          in: query
          type: array
          collectionFormat: csv
          items:
            type: string
        - name: role
          in: query
          type: string
          enum:
          - user
          - admin

##########
# Models #
##########
definitions:
  User2:
    type: object
    properties:
      name:
        type: string
      display_name:
        type: string
      role:
        type: string
        enum:
        - user
        - admin
      last_login:
        type: string
        format: date-time