Building API in Rails - Part 6

Hi there, welcome to the sixth part of Building API with Rails series. In this part we are going to generate a Swagger documentation for our API by using a gem known as rswag. Swagger doc

Work vector created by storyset - www.freepik.com

Adding the rswag gem

The first thing we need to do is add the rswag gem along with some other necessary gem and do bundle install.

# for api documentation
gem 'rswag'
gem 'rswag-api'
gem 'rswag-ui'

group :development, :test do
  gem 'rswag-specs'
end

Setting up rswag

Now after we have added the necessary we just need to run the install generator in the terminal.

rails g rswag:install

This will create some initializers and helper files as This will create some initializers and helper files as

1. spec/swagger_helper.rb
2. config/initializers/rswag_api.rb
3. config/initializers/rswag-ui.rb

I am going to change the default configuration at spec/swagger_helper.rb to something like this

  config.swagger_docs = {
    'v1/swagger.json' => {
      swagger: '2.0',
      info: {
        title: 'API V1',
        version: '1.0.0',
        description: 'API Docs'
      },
      paths: {},
      schemes: %w[
        http
        https
      ],
      consumes: ['application/json'],
      servers: [
        {
          url: 'http://{defaultHost}',
          variables: {
            defaultHost: {
              default: 'localhost:3000'
            }
          }
        }
      ]
    }
  }

  ....
  config.swagger_format = :json

Also for the initializer rswag-ui.rb

  ...
  c.swagger_endpoint '/api-docs/v1/swagger.json', 'API V1 Docs'
  ...

Generate spec file

In order to generate a swagger documentation we need to write our test cases in rswag-spec format. For this we can generate the spec file easily with a generator.

Let’s generate a spec file for our users controller, goto terminal and run

rails generate rspec:swagger API::V1::Users_Controller

Since we already has a user_spec.rb file it will give us a warning. Just press y and enter.

Updating the users spec and making it pass

The spec needed to generate the Swagger documentation is similar to the rspec just need some parameters defined. The user_spec.rb will look something like this now

  require 'swagger_helper'

  RSpec.describe 'api/v1/users', type: :request do
    let!(:user1) { create(:user, email: 'test@1.com') }
    let!(:user2) { create(:user, email: 'test@2.com') }
    let!(:total_users) { User.all.count }

    path '/api/v1/users' do
      get('list users') do
        response(200, 'successful') do
          tags 'Users'
          after do |example|
            example.metadata[:response][:content] = {
              'application/json' => {
                example: JSON.parse(response.body, symbolize_names: true)
              }
            }
          end
          run_test! do |response|
            resp = JSON.parse(response.body, symbolize_names: true)
            expect(resp[:data].count).to eq(total_users)
          end
        end
      end

      post('create user') do
        parameter name: :user_params, in: :body, schema: {
          properties: {
            user: {
              type: :object,
              properties: {
                fullname: { type: :string, required: true },
                email: { type: :string, required: true },
                password: { type: :string, required: true }
              }
            }
          }
        }

        response(201, 'successful') do
          tags 'Users'
          request_body = {
            user: {
              fullname: 'New User',
              email: 'new@gmail.com',
              password: 'asdasd12ad'
            }
          }

          let!(:user_params) { request_body }

          after do |example|
            example.metadata[:response][:content] = {
              'application/json' => {
                example: JSON.parse(response.body, symbolize_names: true)
              }
            }
          end
          run_test! do |_response|
            expect(User.all.count).to eq(total_users + 1)
          end
        end
      end
    end

    path '/api/v1/users/{id}' do
      parameter name: 'id', in: :path, type: :string, description: 'User id'

      get('show user') do
        tags 'Users'
        response(200, 'successful') do
          let(:id) { user1.id }

          after do |example|
            example.metadata[:response][:content] = {
              'application/json' => {
                example: JSON.parse(response.body, symbolize_names: true)
              }
            }
          end
          run_test! do |response|
            resp = JSON.parse(response.body, symbolize_names: true)
            expect(resp[:data][:attributes][:email]).to eq(user1.email)
          end
        end
      end

      patch('update user') do
        parameter name: :user_params, in: :body, schema: {
          properties: {
            user: {
              type: :object,
              properties: {
                fullname: { type: :string, required: false }
              }
            }
          }
        }

        response(200, 'successful') do
          tags 'Users'
          request_body = {
            user: {
              fullname: 'Updated User Name'
            }
          }

          let!(:user_params) { request_body }
          let(:id) { user1.id }

          after do |example|
            example.metadata[:response][:content] = {
              'application/json' => {
                example: JSON.parse(response.body, symbolize_names: true)
              }
            }
          end
          run_test! do |_response|
            user1.reload
            expect(user1.fullname).to eq('Updated User Name')
          end
        end
      end

      delete('delete user') do
        response(200, 'successful') do
          tags 'Users'
          let(:id) { user1.id }

          after do |example|
            example.metadata[:response][:content] = {
              'application/json' => {
                example: JSON.parse(response.body, symbolize_names: true)
              }
            }
          end
          run_test! do |_response|
            expect(User.all.count).to eq(total_users - 1)
          end
        end
      end
    end

    path '/api/v1/change_password' do
      parameter name: 'Authorization', in: :header, type: :string

      patch('change_password user') do
        parameter name: :user_params, in: :body, schema: {
          properties: {
            user: {
              type: :object,
              properties: {
                password: { type: :string, required: true }
              }
            }
          }
        }

        response(200, 'successful') do
          tags 'Passwords'
          request_body = {
            user: {
              password: 'updated password'
            }
          }

          let!(:user_params) { request_body }
          let!(:Authorization) { 'Bearer ' + JsonWebToken.encode(user_id: user1.id) }
          after do |example|
            example.metadata[:response][:content] = {
              'application/json' => {
                example: JSON.parse(response.body, symbolize_names: true)
              }
            }
          end
          run_test!
        end
      end
    end
  end

Simillary, for session_spec.rb

  require 'swagger_helper'

  RSpec.describe 'api/v1/sessions', type: :request do
    let!(:user1) { create(:user, email: 'test@1.com') }

    path '/api/v1/login' do
      parameter name: :login_params, in: :body, schema: {
        properties: {
          user: {
            type: :object,
            properties: {
              email: { type: :string, required: true },
              password: { type: :string, required: true }
            }
          }
        }
      }

      post('login session') do
        response(200, 'successful') do
          tags 'Sessions'

          let!(:user1) { create(:user, email: 'test@1.com') }
          request_body = {}

          before do |_example|
            request_body = {
              user: {
                email: user1.email,
                password: user1.password
              }
            }
          end

          let!(:login_params) { request_body }

          after do |example|
            example.metadata[:response][:content] = {
              'application/json' => {
                example: JSON.parse(response.body, symbolize_names: true)
              }
            }
          end
          run_test! do |response|
            resp = JSON.parse(response.body, symbolize_names: true)
            token = JsonWebToken.encode(user_id: user1.id)
            expect(resp[:data][:attributes][:auth_token]).to eq(token)
          end
        end
      end
    end
  end

Generate the Swagger document

Now, the only thing we have left to do is generate the swagger.json file. For this also we can just use the genrator.

In the terminal paste this command

SWAGGER_DRY_RUN=0 RAILS_ENV=test rails rswag

Now if we start the rails server and goto localhost:3000/api-docs we can see the beautifud Swagger documentation UI.

Swagger doc UI

Great :tada: :tada: we have come to the end of the part 6 and thanks for reading till the end. The code base is available here. :beers:

Please feel free to give your feedback on the comment section below or ping me at or . Have a great time :smiley_cat:

Building API in Rails - Part 6