Duetcode

API documentation with Swagger

02 August 2020

Welcome to the seventh chapter of the API-only ruby on rails course. This time, we will implement the first part of the swagger documentation for the API endpoints. If you didn’t read the previous sections yet, you can check the content list from the overview page.

What is swagger?

I assume most of you already know or used swagger for the API documentation and testing, but let me quickly explain why we want to use swagger. Swagger is a specification (recently called OpenAPI specification) that allows you to describe available API endpoints and operations on each endpoint using a JSON or YAML file. By using the swagger file with swagger UI, we can interactively document and test API endpoints. You can check the example swagger file here. You can also see how this file looks like using together with swagger UI here. As you can see, swagger UI allows client developers to see the living example of the API endpoints. They can see all the details of requests and responses for each endpoint.

How to integrate swagger?

For the swagger integration with rails applications, there are some different approaches. Some of them are using tests to document API. Using test files to generate API documentation feels like mixing two big responsibility into the one. Let’s say you have a strict deadline, which you might think about not maintaining the documentation for a while but want to have tests anyway. Using both of them in the same place will make it challenging to deal with that situation. You might think like “doing one of them is already covers both of them” but actually, you need to have more extended specs than the usual in case you want to generate swagger documentation from the specs.

The other approach is auto-generating the swagger documentation. Even though those libraries are announced as auto-generated, you still need to work on them. The nature of the ruby (a dynamically typed language) doesn’t allow you to understand the types of request parameters or response payload fields. Or if you have a rescue block for all API endpoints for the 404 (not found) response, you need to document it anyway manually. So it’s half automation, half manual maintenance work.

Another approach is writing the documentation explicitly, which we will use along with the course. It will add maintenance costs to the development process, but it’s the easiest and pretty straightforward option.

Integrate swagger-blocks gem

I would like to use swagger-blocks gem to integrate swagger since it’s aligned with the third option that we discussed. The gem has a DSL that allows us to write API documentation manually. Also, we will create a controller which generates and exposes a JSON file from the code that we write by using swagger-blocks DSL. (After we have a swagger JSON, we will integrate swagger-ui to show them interactively in the next chapters.)

Let’s start by adding swagger-blocks gem to the Gemfile and then run bundle install command.

Gemfile
# ...

# Define and serve live-updating Swagger JSON for Ruby apps.
gem 'swagger-blocks', '~> 3.0'

# ...

Now we will follow the instructions from the documentation of the swagger-blocks gem. Let’s start by creating app/controllers/apidocs_controller.rb file.

app/controllers/apidocs_controller.rb
# frozen_string_literal: true

class ApidocsController < ActionController::Base
  include Swagger::Blocks

  swagger_root do
    key :swagger, '2.0'

    info do
      key :version, '1.0.0'
      key :title, 'Bookmarker API'
      key :description, 'Bookmarker API documentation'

      contact do
        key :name, 'duetcode.io'
      end
    end

    key :host, 'localhost:3000'
    key :basePath, '/api/v1'
    key :consumes, ['application/json']
    key :produces, ['application/json']
    key :schemes,  ['http']
  end

  # A list of all classes that have swagger_* declarations.
  SWAGGERED_CLASSES = [
    self
  ].freeze

  def index
    render json: Swagger::Blocks.build_root_json(SWAGGERED_CLASSES)
  end
end

Here, we defined the general information of the API documentation with specifying version, title, host, basePath, etc. We will include other classes that describe the endpoints and resources to the SWAGGERED_CLASSES, but we can ignore it for now. With the index action, we’re building the JSON file using all classes specified. We don’t have any other class which uses swagger-blocks than ApidocsController at the moment.

And now, we need to add apidocs#index action to the routes.

config/routes.rb
# frozen_string_literal: true

Rails.application.routes.draw do
  # ...

  resources :apidocs, only: [:index]
end

If you go to the http://localhost:3000/apidocs address in your browser, it will render the swagger file generated, but we have only the general information of the API for now. Let’s add the necessary documentation classes for the user creation endpoint.

It’s better to have a namespace for the classes we would like to use with swagger. Therefore, let’s create a swagger folder in the app/controllers folder. Then, inside of the swagger folder create controllers and models folders. Controllers will reflect the classes that we use to document API endpoints, while models will reflect the request and response payloads.

Let’s start by creating app/controllers/swagger/models/user_input.rb. It’s the file we define the request payload of the user creation endpoint.

app/controllers/swagger/models/user_input.rb
# frozen_string_literal: true

module Swagger::Models::UserInput
  include Swagger::Blocks

  swagger_schema :UserInput do
    key :required, %i[user]
    property :user do
      key :type, :object
      key :required, %i[email password]

      property :email do
        key :type, :string
      end

      property :password do
        key :type, :string
      end
    end
  end
end

Here, we basically defined the request payload fields for the POST api/v1/users endpoint. After that we need to add this file to the SWAGGERED_CLASSES inside the apidocs_controller.rb file.

app/controllers/apidocs_controller.rb
# frozen_string_literal: true

class ApidocsController < ActionController::Base
  # ..

  # A list of all classes that have swagger_* declarations.
  SWAGGERED_CLASSES = [
    Swagger::Models::UserInput,
    self
  ].freeze

  def index
    render json: Swagger::Blocks.build_root_json(SWAGGERED_CLASSES)
  end
end

And now we can document the user creation API endpoint. To do this, let’s create the app/controllers/swagger/controllers/users_controller.rb file.

app/controllers/swagger/controllers/users_controller.rb
# frozen_string_literal: true

class Swagger::Controllers::UsersController
  include Swagger::Blocks

  swagger_path '/users' do
    operation :post do
      key :description, 'Creates a new user in the system'
      key :tags, [
        'user'
      ]

      parameter do
        key :name, :user
        key :in, :body
        key :description, 'Email and password information of the new user'
        key :required, true
        schema do
          key :'$ref', :UserInput
        end
      end
    end
  end
end

We added the request information for the POST request of the user resource. We then defined the request details by specifying the UserInput class that we described above as a request payload.

And again let’s add it to the ApidocsController, as we similarly did with the UserInput.

app/controllers/apidocs_controller.rb
class ApidocsController < ActionController::Base
  # ..

  # A list of all classes that have swagger_* declarations.
  SWAGGERED_CLASSES = [
    Swagger::Controllers::UsersController,
    Swagger::Models::UserInput,
    self
  ].freeze

  def index
    render json: Swagger::Blocks.build_root_json(SWAGGERED_CLASSES)
  end
end

And if you go to the http://localhost:3000/apidocs you will see the generated JSON with the recent documentation we added with classes. For now, we don’t have any user interface to see what it looks like, but you can copy the generated file into the swagger editor and check how it works with swagger-ui. (To do that, you also need to adjust CORS settings, which we didn’t in the course yet!)

Summary

In this chapter, we started to build API documentation by using swagger. We don’t have a user interface yet, but we have an action that generates the swagger file. In the next chapters, we will add responses to the API documentation along with the status codes and payloads. We will also integrate swagger UI into the project. You can find the source code of bookmarker application on github. You can also find the previous chapter here.

Thank you for reading! If you want to be notified when I publish a new chapter, you can follow me on twitter.