After learning the basics and having written a little bit huge file for a so simple API, you may be concerned by what nightmare it could be to handle a bigger and more complex API. REST assured that the OpenAPI Specification (formerly Swagger Specification) format offers all means to write really small and simple specification files whatever the described API’s size and complexity.

Writing OpenAPI (fka Swagger) Specification tutorial

This tutorial is composed of several posts:

If you’re a bit lost in the specification, take a look at my visual documentation:

In this third part you will learn how to simplifiy the specification file by defining reusable definitions, responses and parameters and using them with references and thus make the writing and reading of OpenAPI Specification fairly easy.

Simplifying data model description

We’ll use the final example of the previous part as starting point. When taking a look at this specification file, the obvious problem is that a Person is defined three times:

swagger: "2.0"

info:
  version: 1.0.0
  title: Simple API
  description: A simple API to learn how to write OpenAPI Specification

schemes:
  - https
host: simple.api
basePath: /openapi101

paths:
  /persons:
    get:
      summary: Gets some persons
      description: Returns a list containing all persons. The list supports paging.
      parameters:
       - name: pageSize
         in: query
         description: Number of persons returned
         type: integer
       - name: pageNumber
         in: query
         description: Page number
         type: integer
      responses:
        200:
          description: A list of Person
          schema:
            type: array
            items:
              required:
                - username
              properties:
                firstName:
                  type: string
                lastName:
                  type: string
                username:
                  type: string
    post:
      summary: Creates a person
      description: Adds a new person to the persons list.
      parameters:
        - name: person
          in: body
          description: The person to create.
          schema:
            required:
              - username
            properties:
              firstName:
                type: string
              lastName:
                type: string
              username:
                type: string
      responses:
        204:
          description: Persons succesfully created.
        400:
          description: Persons couldn't have been created.
  /persons/{username}:
    get:
      summary: Gets a person
      description: Returns a single person for its username.
      parameters:
        - name: username
          in: path
          required: true
          description: The person's username
          type: string
      responses:
        200:
          description: A Person
          schema:
            required:
              - username
            properties:
              firstName:
                type: string
              lastName:
                type: string
              username:
                type: string
        404:
          description: The Person does not exists.

By using reusable definitions this not so simple specification will be transformed into this one:

swagger: "2.0"

info:
  version: 1.0.0
  title: Simple API
  description: A simple API to learn how to write OpenAPI Specification

schemes:
  - https
host: simple.api
basePath: /openapi101

paths:
  /persons:
    get:
      summary: Gets some persons
      description: Returns a list containing all persons. The list supports paging.
      parameters:
       - name: pageSize
         in: query
         description: Number of persons returned
         type: integer
       - name: pageNumber
         in: query
         description: Page number
         type: integer
      responses:
        200:
          description: A list of Person
          schema:
            $ref: "#/definitions/Persons"
    post:
      summary: Creates a person
      description: Adds a new person to the persons list.
      parameters:
        - name: person
          in: body
          description: The person to create.
          schema:
            $ref: "#/definitions/Person"
      responses:
        204:
          description: Persons succesfully created.
        400:
          description: Persons couldn't have been created.
  /persons/{username}:
    get:
      summary: Gets a person
      description: Returns a single person for its username.
      parameters:
        - name: username
          in: path
          required: true
          description: The person's username
          type: string
      responses:
        200:
          description: A Person
          schema:
            $ref: "#/definitions/Person"
        404:
          description: The Person does not exists.

definitions:
  Person:
    required:
      - username
    properties:
      firstName:
        type: string
      lastName:
        type: string
      username:
        type: string
  Persons:
    type: array
    items:
      $ref: "#/definitions/Person"

The OpenAPI Specification definitions section (Swagger Object) allows you to define once and for all objects/entities/models that can be used anywhere in the specification (i.e. where a schema is defined).

Adding definitions section

We’ll start by adding a new definitions section at the end of the document (nb: it can be placed anywhere as long as it’s on the root of the OpenAPI Specification tree structure).

        404:
          description: The Person does not exists.

definitions:

Defining a reusable definition

Then we define a Person once and for all in the definitions section:

definitions:
  Person:
    required:
      - username
    properties:
      firstName:
        type: string
      lastName:
        type: string
      username:
        type: string

Note that the information provided in Person are exactly the same as the one provided in the three schemas describing a person in previous version. A definition is simply a named schema object.

Referencing a definition from another definition

We’ll start using this newly created definition by referencing it in another one. As we have defined a Person we’ll also define Persons which is an array (or a list) of Persons. The information provided in Persons are almost the same as the one provided in the get /persons response:

          description: A list of Person
          schema:
            type: array
            items:
              required:
                - username
              properties:
                firstName:
                  type: string
                lastName:
                  type: string
                username:
                  type: string

The only difference is that the schema describing the array’s items has been replaced by a reference ($ref) to the Persons definition.

  Persons:
    type: array
    items:
      $ref: "#/definitions/Person"

A reference is only a path to another declaration within the OpenAPI Specification.

Using definitions in responses

Once we have defined Person and Persons, we can use them to replace the inline schemas by references in all operations responses.

get /persons

Before

      responses:
        200:
          description: A list of Person
          schema:
            type: array
            items:
              required:
                - username
              properties:
                firstName:
                  type: string
                lastName:
                  type: string

After

      responses:
        200:
          description: A list of Person
          schema:
            $ref: "#/definitions/Persons"

get /persons/{username}

Before

      responses:
        200:
          description: A Person
          schema:
            required:
              - username
            properties:
              firstName:
                type: string
              lastName:
                type: string
              username:
                type: string

After

      responses:
        200:
          description: A Person
          schema:
            $ref: "#/definitions/Person"

Using definitions in parameters

Definitions are not only meant to be used within the definitions section (it would be pointless), they can also be used in operations parameters.

post /persons:

Before

    post:
      summary: Creates a person
      description: Adds a new person to the persons list.
      parameters:
        - name: person
          in: body
          description: The person to create.
          schema:
            required:
              - username
            properties:
              firstName:
                type: string
              lastName:
                type: string
              username:
                type: string

After

    post:
      summary: Creates a person
      description: Adds a new person to the persons list.
      parameters:
        - name: person
          in: body
          description: The person to create.
          schema:
            $ref: "#/definitions/Person"

Simplifying responses description

Now we have discovered references ($ref), we’ll see they can be used for other matters like responses definition.

swagger: "2.0"

info:
  version: 1.0.0
  title: Simple API
  description: A simple API to learn how to write OpenAPI Specification

schemes:
  - https
host: simple.api
basePath: /openapi101

paths:
  /persons:
    get:
      summary: Gets some persons
      description: Returns a list containing all persons. The list supports paging.
      parameters:
       - name: pageSize
         in: query
         description: Number of persons returned
         type: integer
       - name: pageNumber
         in: query
         description: Page number
         type: integer
      responses:
        200:
          description: A list of Person
          schema:
            $ref: "#/definitions/Persons"
        500:
          $ref: "#/responses/Standard500ErrorResponse"
    post:
      summary: Creates a person
      description: Adds a new person to the persons list.
      parameters:
        - name: person
          in: body
          description: The person to create.
          schema:
            $ref: "#/definitions/Person"
      responses:
        204:
          description: Persons succesfully created.
        400:
          description: Persons couldn't have been created.
        500:
          $ref: "#/responses/Standard500ErrorResponse"
          
  /persons/{username}:
    get:
      summary: Gets a person
      description: Returns a single person for its username.
      parameters:
        - name: username
          in: path
          required: true
          description: The person's username
          type: string
      responses:
        200:
          description: A Person
          schema:
            $ref: "#/definitions/Person"
        404:
          description: The Person does not exists.
        500:
          $ref: "#/responses/Standard500ErrorResponse"

definitions:
  Person:
    required:
      - username
    properties:
      firstName:
        type: string
      lastName:
        type: string
      username:
        type: string
  Persons:
    type: array
    items:
      $ref: "#/definitions/Person"
  Error:
    properties:
      code:
        type: string
      message:
        type: string

responses:
  Standard500ErrorResponse:
    description: An unexpected error occured.
    schema:
      $ref: "#/definitions/Error"

Defining a reusable HTTP 500 response

Let’s say, we want every API’s operation to return an error code and message to provide more detailed information about what happened when an operation failed with an HTTP 500 error. If we do that the basic way, we’ll end adding a 500 response on each operation like this:

paths:
  /persons:
    get:
      summary: Gets some persons
      description: Returns a list containing all persons. The list supports paging.
      parameters:
       - name: pageSize
         in: query
         description: Number of persons returned
         type: integer
       - name: pageNumber
         in: query
         description: Page number
         type: integer
      responses:
        200:
          description: A list of Person
          schema:
            $ref: "#/definitions/Persons"
        500:
          description: An unexpected error occured.
          schema:
            properties:
              code:
                type: string
              message:
                type: string
    post:
      summary: Creates a person
      description: Adds a new person to the persons list.
      parameters:
        - name: person
          in: body
          description: The person to create.
          schema:
            $ref: "#/definitions/Person"
      responses:
        204:
          description: Persons succesfully created.
        400:
          description: Persons couldn't have been created.
        500:
          description: An unexpected error occured.
          schema:
            properties:
              code:
                type: string
              message:
                type: string

          
  /persons/{username}:
    get:
      summary: Gets a person
      description: Returns a single person for its username.
      parameters:
        - name: username
          in: path
          required: true
          description: The person's username
          type: string
      responses:
        200:
          description: A Person
          schema:
            $ref: "#/definitions/Person"
        404:
          description: The Person does not exists.
        500:
          description: An unexpected error occured.
          schema:
            properties:
              code:
                type: string
              message:
                type: string

As every operation will handle HTTP 500 error exactly the same way, it’s a pity to have to declare three times exactly the same thing.

Defining an Error definition

But, we have learned that we can define a schema once and for all. So let’s create an Error definition containing string code and message in the definitions section.

definitions:
  Person:
    required:
      - username
    properties:
      firstName:
        type: string
      lastName:
        type: string
      username:
        type: string
  Persons:
    type: array
    items:
      $ref: "#/definitions/Person"
  Error:
    properties:
      code:
        type: string
      message:
        type: string

A first almost good idea would be to use this definition in every operation for 500 response schema.

paths:
  /persons:
    get:
      summary: Gets some persons
      description: Returns a list containing all persons. The list supports paging.
      parameters:
       - name: pageSize
         in: query
         description: Number of persons returned
         type: integer
       - name: pageNumber
         in: query
         description: Page number
         type: integer
      responses:
        200:
          description: A list of Person
          schema:
            $ref: "#/definitions/Persons"
        500:
          description: An unexpected error occured.
          schema:
            $ref: "#/definitions/Error"
    post:
      summary: Creates a person
      description: Adds a new person to the persons list.
      parameters:
        - name: person
          in: body
          description: The person to create.
          schema:
            $ref: "#/definitions/Person"
      responses:
        204:
          description: Persons succesfully created.
        400:
          description: Persons couldn't have been created.
        500:
          description: An unexpected error occured.
          schema:
            $ref: "#/definitions/Error"          
  /persons/{username}:
    get:
      summary: Gets a person
      description: Returns a single person for its username.
      parameters:
        - name: username
          in: path
          required: true
          description: The person's username
          type: string
      responses:
        200:
          description: A Person
          schema:
            $ref: "#/definitions/Person"
        404:
          description: The Person does not exists.
        500:
          description: An unexpected error occured.
          schema:
            $ref: "#/definitions/Error"

Defining a reusable response

But it can be even more simple if we declare a response once and for all within the OpenAPI Specification responses section (Swagger Object). This section allows to define reusable responses definitions.

definitions:
  Person:
    required:
      - username
    properties:
      firstName:
        type: string
      lastName:
        type: string
      username:
        type: string
  Persons:
    type: array
    items:
      $ref: "#/definitions/Person"
  Error:
    properties:
      code:
        type: string
      message:
        type: string

responses:
  Standard500ErrorResponse:
    description: An unexpected error occured.
    schema:
      $ref: "#/definitions/Error"

Note that the response use the Error definition.

Using the defined response

Using a defined response is done through a $ref (just like we did when using definitions).

get /users

      responses:
        200:
          description: A list of Person
          schema:
            $ref: "#/definitions/Persons"
        500:
          $ref: "#/responses/Standard500ErrorResponse"

post /users

      responses:
        204:
          description: Persons succesfully created.
        400:
          description: Persons couldn't have been created.
        500:
          $ref: "#/responses/Standard500ErrorResponse"

get /users/{username}

      responses:
        200:
          description: A Person
          schema:
            $ref: "#/definitions/Person"
        404:
          description: The Person does not exists.
        500:
          $ref: "#/responses/Standard500ErrorResponse"

Simplifying parameters description

Like models/schemas and responses, parameters description can be simplified easily.

swagger: "2.0"

info:
  version: 1.0.0
  title: Simple API
  description: A simple API to learn how to write OpenAPI Specification

schemes:
  - https
host: simple.api
basePath: /openapi101

paths:
  /persons:
    get:
      summary: Gets some persons
      description: Returns a list containing all persons. The list supports paging.
      parameters:
       - $ref: "#/parameters/pageSize"
       - $ref: "#/parameters/pageNumber"
      responses:
        200:
          description: A list of Person
          schema:
            $ref: "#/definitions/Persons"
        500:
          $ref: "#/responses/Standard500ErrorResponse"
    post:
      summary: Creates a person
      description: Adds a new person to the persons list.
      parameters:
        - name: person
          in: body
          description: The person to create.
          schema:
            $ref: "#/definitions/Person"
      responses:
        204:
          description: Person succesfully created.
        400:
          description: Person couldn't have been created.
        500:
          $ref: "#/responses/Standard500ErrorResponse"
          
  /persons/{username}:
    parameters:
      - $ref: "#/parameters/username"
    get:
      summary: Gets a person
      description: Returns a single person for its username.
      responses:
        200:
          description: A Person
          schema:
            $ref: "#/definitions/Person"
        404:
          $ref: "#/responses/PersonDoesNotExistResponse"
        500:
          $ref: "#/responses/Standard500ErrorResponse"
    delete:
      summary: Deletes a person
      description: Delete a single person identified via its username
      responses:
        204:
          description: Person successfully deleted.
        404:
          $ref: "#/responses/PersonDoesNotExistResponse"
        500:
          $ref: "#/responses/Standard500ErrorResponse"
          
  /persons/{username}/friends:
    parameters:
      - $ref: "#/parameters/username"
    get:
      summary: Gets a person's friends
      description: Returns a list containing all persons. The list supports paging.
      parameters:
       - $ref: "#/parameters/pageSize"
       - $ref: "#/parameters/pageNumber"
      responses:
        200:
          description: A person's friends list 
          schema:
            $ref: "#/definitions/Persons"
        404:
          $ref: "#/responses/PersonDoesNotExistResponse"
        500:
          $ref: "#/responses/Standard500ErrorResponse"

definitions:
  Person:
    required:
      - username
    properties:
      firstName:
        type: string
      lastName:
        type: string
      username:
        type: string
  Persons:
    type: array
    items:
      $ref: "#/definitions/Person"
  Error:
    required:
      - code
      - message
    properties:
      code:
        type: string
      message:
        type: string

responses:
  Standard500ErrorResponse:
    description: An unexpected error occured.
    schema:
      $ref: "#/definitions/Error"
  PersonDoesNotExistResponse:
    description: Person does not exist.

parameters:
  username:
    name: username
    in: path
    required: true
    description: The person's username
    type: string
  pageSize:
    name: pageSize
    in: query
    description: Number of persons returned
    type: integer
  pageNumber:
    name: pageNumber
    in: query
    description: Page number
    type: integer
    

Defining a path parameter once for a path

If we add a delete operation to the /persons/{username} path, a first idea could be to do it that way:

  /persons/{username}:
    get:
      summary: Gets a person
      description: Returns a single person for its username.
      parameters:
        - name: username
          in: path
          required: true
          description: The person's username
          type: string
      responses:
        200:
          description: A Person
          schema:
            $ref: "#/definitions/Person"
        404:
          $ref: "#/responses/PersonDoesNotExistResponse"
        500:
          $ref: "#/responses/Standard500ErrorResponse"
    delete:
      summary: Deletes a person
      description: Delete a single person identified via its username
      parameters:
        - name: username
          in: path
          required: true
          description: The person's username
          type: string
      responses:
        204:
          description: Person successfully deleted.
        404:
          $ref: "#/responses/PersonDoesNotExistResponse"
        500:
          $ref: "#/responses/Standard500ErrorResponse"

Note that we had the good idea to create a reusable 404 response.

But having to redefine the username parameter which is shared by each operation on /persons/{username} path is a bit cumbersome. Luckily you can define parameters on path level:

  /persons/{username}:
    parameters:
      - name: username
        in: path
        required: true
        description: The person's username
        type: string
    get:
      summary: Gets a person
      description: Returns a single person for its username.
      responses:
        200:
          description: A Person
          schema:
            $ref: "#/definitions/Person"
        404:
          $ref: "#/responses/PersonDoesNotExistResponse"
        500:
          $ref: "#/responses/Standard500ErrorResponse"
    delete:
      summary: Deletes a person
      description: Delete a single person identified via its username
      responses:
        204:
          description: Person successfully deleted.
        404:
          $ref: "#/responses/PersonDoesNotExistResponse"
        500:
          $ref: "#/responses/Standard500ErrorResponse"

Defining reusable parameters

If we add a get /persons/{username}/friends operations to get a paginated list of a person’s friends, a first idea could be to do it that way:

  /persons/{username}/friends:
    parameters:
      - name: username
        in: path
        required: true
        description: The person's username
        type: string
    get:
      summary: Gets a person's friends
      description: Returns a list containing all persons. The list supports paging.
      parameters:
       - name: pageSize
         in: query
         description: Number of persons returned
         type: integer
       - name: pageNumber
         in: query
         description: Page number
         type: integer
      responses:
        200:
          description: A person's friends list 
          schema:
            $ref: "#/definitions/Persons"
        404:
          $ref: "#/responses/PersonDoesNotExistResponse"
        500:
          $ref: "#/responses/Standard500ErrorResponse"

Note that we reuse the 500 and 404 responses.

But that way, we redefine the username parameter just like on path /persons/{username}:

  /persons/{username}:
    parameters:
      - name: username
        in: path
        required: true
        description: The person's username
        type: string

And we also redefine the pageNumber and pageSize parameters which already exist on get /persons:

  /persons:
    get:
      summary: Gets some persons
      description: Returns a list containing all persons. The list supports paging.
      parameters:
       - name: pageSize
         in: query
         description: Number of persons returned
         type: integer
       - name: pageNumber
         in: query
         description: Page number
         type: integer

Define reusable parameters

As we have done with definitions and responses, we can define reusable parameters within the OpenAPI Specification parameters section (Swagger Object).

parameters:
  username:
    name: username
    in: path
    required: true
    description: The person's username
    type: string
  pageSize:
    name: pageSize
    in: query
    description: Number of persons returned
    type: integer
  pageNumber:
    name: pageNumber
    in: query
    description: Page number
    type: integer

Use reusable parameters

These reusable parameters can be used with a reference just like definitions and responses.

get /persons

Before

  /persons:
    get:
      summary: Gets some persons
      description: Returns a list containing all persons. The list supports paging.
      parameters:
       - name: pageSize
         in: query
         description: Number of persons returned
         type: integer
       - name: pageNumber
         in: query
         description: Page number
         type: integer

After

  /persons:
    get:
      summary: Gets some persons
      description: Returns a list containing all persons. The list supports paging.
      parameters:
       - $ref: "#/parameters/pageSize"
       - $ref: "#/parameters/pageNumber"

get and delete /persons/{username}

Before

  /persons/{username}:
    parameters:
      - name: username
        in: path
        required: true
        description: The person's username
        type: string

After

  /persons/{username}:
    parameters:
      - $ref: "#/parameters/username"

get /persons/{username}/friends

Before

  /persons/{username}/friends:
    parameters:
      - name: username
        in: path
        required: true
        description: The person's username
        type: string
    get:
      summary: Gets a person's friends
      description: Returns a list containing all persons. The list supports paging.
      parameters:
       - name: pageSize
         in: query
         description: Number of persons returned
         type: integer
       - name: pageNumber
         in: query
         description: Page number
         type: integer

After

  /persons/{username}/friends:
    parameters:
      - $ref: "#/parameters/username"
    get:
      summary: Gets a person's friends
      description: Returns a list containing all persons. The list supports paging.
      parameters:
       - $ref: "#/parameters/pageSize"
       - $ref: "#/parameters/pageNumber"

Conclusion

By defining reusable definitions, responses and parameters and using them with references you can easily write OpenAPI Specification files which are simple and easily understandable. In next post, we’ll dig deeper in various aspect of the OpenAPI Specification.