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.
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.
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.
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.
# ...
# 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.
# 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.
# 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.
# 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.
# 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.
# 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
.
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!)
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.