Producing Documentation for Your Rails API

Written by: Leigh Halliday

Why is it such a joy to work with Stripe or Shopify as a developer? It could be for a number of reasons, but one of them is surely that they have great documentation.

For these companies, documentation isn't an afterthought -- it's something you can tell that they obviously spend a lot of time and resources on. Why is this? For one reason, they're both businesses started by developers, but it also just makes good business sense. When APIs are easy to understand and integrate with, developers are able to get up and running in less time.

Documentation may not be the first thing you think about when beginning to design a new API, but maybe it should be. In this article, let's talk about some tools that exist in the Ruby/Rails ecosystem (along with some more generalized ones) for producing documentation for your API. We'll go through some examples of generating docs which are produced through testing.

Recommendations

Before we get into the specifics of some of the tools we can use to document and publish that documentation, let's take a step back and talk about a few recommendations for when it comes to documenting our APIs.

Prioritize documentation

Writing good documentation for your API should not be an afterthought. In fact, there's an idea called Documentation Driven Development that suggests that, much like TDD (where you write your tests before the implementation itself), you should write your documentation before writing the actual implementation.

By prioritizing documentation, you will guarantee that you'll never run out of time for it. It will also cause you to slow down enough to think through whether the implementation makes sense as you try to explain it to someone else.

Make documentation easy

It should not be difficult to write documentation for your API. I don't mean that it doesn't take time and hard work to write good documentation. Rather, we should be removing barriers or annoyances that might stop a developer from documenting the API they're working on. If it's a pain to synchronize, if it's hard to deploy, if the formatting is difficult or too repetitive, people aren't going to want to do it.

But it doesn't have to be painful. Documentation should be automated as much as possible and should be deployed via some sort of continuous integration system. It should also be kept in a source control repository to easily view changes being made to it.

Keep documentation up to date

The only thing worse than no documentation is out of date documentation. It's like following a map with incorrect or incomplete directions. When the app/API is updated, the corresponding documentation should be updated at the same time.

Updating the API documentation should be part of the development process. Just like code isn't complete until it's been tested and peer reviewed, it also shouldn't be marked as completed until the documentation has been brought up to date as well.

Write your own client

Once you have documentation, it's a good idea to have someone who isn't intimate with how the code works use that documentation to build some sort of client to connect to the API. This is what your customers and clients will experience.

If it's confusing, missing details, or requires lots of back and forth with your development team to make things work, then it's telling you one of two things: Either your documentation needs work and further clarity, or perhaps the API itself was designed poorly. Until you've been the client, it's very hard to know this. To use the phrase coined by Microsoft in the '80s: Eat your own dog food.

What Your Documentation Should Include

There is a difference between documenting your API holistically and only providing an API reference (endpoints, parameters, headers, responses, errors, etc.). Providing an API reference is a good start, but it shouldn't be the whole story.

Stripe has just released a new Getting Started guide that's a great example of this. It's all about giving the developer the guidance and knowledge needed to get started. API references are for those who are already familiar with how the API works and are in need of specifics.

Here are some ideas for different sections that your API can and should have:

  • Getting started: A quick introduction which only includes the basics about how to get going. The bare minimum needed to connect to and use the API.

  • Tutorials: These can include use cases surrounding different common flows or uses of the application. Examples: Pay a bill, Refund a purchase, Update user settings, etc.

  • API Reference: This is where the documentation lives regarding the endpoints available, which parameters and headers they receive, HTTP status codes, what the response looks like, what errors may be triggered, and examples that may be in multiple programming languages.

  • Authentication: Is your API available for public consumption, or is an API token needed? Here is where you can talk about throttling and other areas around security/fair usage.

Generate Documentation through Testing

One popular way to generate documentation, more specifically around having an API reference, is to do so through testing. Rails comes with a couple popular gems for doing this: apipie-rails and rspec_api_documentation.

Let's take a look at how it's done using rspec_api_documentation.

First things first, we'll need to be using RSpec instead of the default test framework which comes with Rails. This isn't a huge problem; it's hugely popular and is normally one of the first things I change after generating a new Rails app. After that, we can install the rspec_api_documentation gem.

This gem has a lot of configuration options you can set. One of them includes the type of output you want produced.

By default, it produces a fairly vanilla/unstyled set of HTML documents put inside of the doc/api folder. By switching the output format to :json, we can use this data to generate more complicated styling in combination with other tools.

apitome is one of them. It takes the documentation output and puts it into a much nicer format, including a number of common libraries such as bootstrap and highlight.js. It can be single or multi page.

# config/initializers/rspec_api_documentation.rb
RspecApiDocumentation.configure do |config|
  # Output folder
  config.docs_dir = Rails.root.join("doc", "api")
  # An array of output format(s).
  # Possible values are :json, :html, :combined_text, :combined_json,
  #   :json_iodocs, :textile, :markdown, :append_json
  config.format = [:json]
end

Let's take a look at what the actual tests look like. These are what the documentation is generated from.

The app/models we're working with are for a (made-up) real estate API that allows us to sort and query houses, condos, etc. That part of the code isn't too important here, so we'll just look at the test itself.

# spec/acceptance/residences_spec.rb
require 'rails_helper'
require 'rspec_api_documentation/dsl'
resource "Residences" do
  let(:user) { create(:user) }
  let(:auth_token) { user.auth_token }
  # Headers which should be included in the request
  header "Accept", "application/vnd.api+json"
  header "X-Api-Key", :auth_token
  # A specific endpoint
  get "/residences" do
    # Which GET/POST params can be included in the request and what do they do?
    parameter :sort, "Sort the response. Can be sorted by #{ResidencesIndex::SORTABLE_FIELDS.join(',')}. They are comma separated and include - in front to sort in descending order. Example: -rooms,cost"
    parameter :number, "Which page number of results would you like.", scope: :page
    let(:number) { 1 }
    let(:sort) { "-rooms,cost" }
    # We can provide multiple examples for each endpoint, highlighting different aspects of them.
    example "Listing residences" do
      explanation "Retrieve all of the residences. They can be sorted, filtered and will be paginated."
      2.times { create(:residence, rooms: (1..6).to_a.sample) }
      do_request
      expect(status).to eq(200)
    end
  end
end

It looks similar to the DSL provided by RSpec by default but introduces a few extra methods:

  • header

  • parameter

  • explanation

  • among others

These allow us to describe how the endpoint works, provide examples, and document the different inputs and outputs of each endpoint.

By running bundle exec rake docs:generate, we not only see if the tests pass (it will stop us from generating the documentation if one is failing), but it will also generate the output file we have specified in our config file.

Here is the index file it generates, which points to other JSON files specific to a single resource:

{
  "resource": "Residences",
  "http_method": "GET",
  "route": "/residences",
  "description": "Listing residences",
  "explanation": "Retrieve all of the residences. They can be sorted, filtered and will be paginated.",
  "parameters": [
    {
      "name": "sort",
      "description": "Sort the response. Can be sorted by rooms,cost,bathrooms,square_feet. They are comma separated and include - in front to sort in descending order. Example: -rooms,cost"
    },
    {
      "scope": "page",
      "name": "number",
      "description": "Which page number of results would you like."
    }
  ],
  "response_fields": [
  ],
  "requests": [
    {
      "request_method": "GET",
      "request_path": "/residences?sort=-rooms%2Ccost&page[number]=1",
      "request_body": null,
      "request_headers": {
        "Accept": "application/vnd.api+json",
        "X-Api-Key": "bddc333e-dffc-44a9-b2dc-244dfd1f2abf",
        "Host": "example.org",
        "Cookie": ""
      },
      "request_query_parameters": {
        "sort": "-rooms,cost",
        "page": {
          "number": "1"
        }
      },
      "request_content_type": null,
      "response_status": 200,
      "response_status_text": "OK",
      "response_body": "{\n  \"data\": [\n    {\n      \"id\": \"1\",\n      \"type\": \"residences\",\n      \"attributes\": {\n        \"residence_type\": \"Detached\",\n        \"square_feet\": 2500,\n        \"rooms\": 5,\n        \"bathrooms\": 3,\n        \"address\": \"123 Blue Jays Way\",\n        \"city\": \"Toronto\",\n        \"province\": \"ON\",\n        \"country\": \"CA\",\n        \"postal_code\": \"M8X5B2\",\n        \"latitude\": null,\n        \"longitude\": null,\n        \"cost\": \"600000.0\"\n      }\n    },\n    {\n      \"id\": \"2\",\n      \"type\": \"residences\",\n      \"attributes\": {\n        \"residence_type\": \"Detached\",\n        \"square_feet\": 2500,\n        \"rooms\": 1,\n        \"bathrooms\": 3,\n        \"address\": \"123 Blue Jays Way\",\n        \"city\": \"Toronto\",\n        \"province\": \"ON\",\n        \"country\": \"CA\",\n        \"postal_code\": \"M8X5B2\",\n        \"latitude\": null,\n        \"longitude\": null,\n        \"cost\": \"600000.0\"\n      }\n    }\n  ],\n  \"links\": {\n    \"self\": \"http://example.org/residences?page%5Bnumber%5D=1&sort=-rooms%2Ccost\",\n    \"first\": \"http://example.org/residences?page%5Bnumber%5D=1&sort=-rooms%2Ccost\",\n    \"prev\": \"http://example.org/residences?page%5Bnumber%5D=1&sort=-rooms%2Ccost\",\n    \"next\": \"http://example.org/residences?page%5Bnumber%5D=1&sort=-rooms%2Ccost\",\n    \"last\": \"http://example.org/residences?page%5Bnumber%5D=1&sort=-rooms%2Ccost\"\n  }\n}",
      "response_headers": {
        "X-Frame-Options": "SAMEORIGIN",
        "X-XSS-Protection": "1; mode=block",
        "X-Content-Type-Options": "nosniff",
        "Content-Type": "application/json; charset=utf-8",
        "ETag": "W/\"4475a76defed21679ee33bd17c2b9fe2\"",
        "Cache-Control": "max-age=0, private, must-revalidate",
        "X-Request-Id": "0fb2f89c-9e60-4487-a5f2-e566aa31002f",
        "X-Runtime": "0.020782",
        "Content-Length": "969"
      },
      "response_content_type": "application/json; charset=utf-8",
      "curl": null
    }
  ]
}

Because it's just JSON, we're free to take this and produce our docs using a static website builder such as Jekyll. This gives us the freedom and flexibility to add examples in different programming languages, make our documentation as interactive as we would like, but at the same time keep it up to date with the documentation that our app is producing.

We can either move the files into the Jekyll project manually or set the output directory to put them in the correct data folder automatically.

Tooling/Products

If you aren't using Rails or would prefer to not have your tests intermingled with your documentation, there's a great tool called Slate by Tripit which allows us to write documentation in markdown. It closely resembles the beautiful three-panel (with multi-language examples) documentation like Stripe.

A couple other tools to investigate are API Blueprint and Apiary, a paid solution that also comes with a free tier.

Conclusion

I hope with this article I've been able to introduce a few new tools that can be used to generate great documentation for your API.

By making the lives of the developers who need to use your API easier, you're not only building goodwill in the community but you're most likely generating more business for your company. The quicker a developer can understand and finish up the integration with your API, the fewer headaches your team will have when providing support.

Stay up to date

We'll never share your email address and you can opt out at any time, we promise.