How to document your Grape API using Swagger UI
Why?
Maintaining an API documentation can be very time consuming.
In the best case, you'll update the doc before each update, according to the
new specifications, but in the real world, you have a deadline, and your
colleagues are stuck waiting for your new feature to be implemented.
In this case, you might forget to update the documentation :)
The solution is to use a tool that will dynamically generate this documentation.
Swagger
There is no magic, no tools will be able to read your source code and generate
documentation for you.
The Swagger project tries to
solve this issue by defining a generic schema to describe api endpoint's
methods using the JSON format.
This format is not very human friendly so you don't want to write this file
yourself. Fortunately, there are many tools that can generate a Swagger compliant
specification file automatically from your codebase. So let's have a look at
this paragraph in the
Swagger wiki
to find the tool that fits your needs.
Swagger UI
Swagger UI is a web interface to
test, consume and discover your API without pain.
It shows all you api endpoints and methods, and generates pretty HTML forms to
send queries through these methods.
Swagger UI doesn't require server side components, so it can be installed as a
static website.
In practice
If you're in a hurry, there is an online demo,
else we will start building ours.
There is a github repository containing the full code explained in this section:
https://github.com/AF83/grape-swaggerui-example
For this example, I chose RubyOnRails and
Grape.
Install Grape
Given you have a new rails 4.0.0 application, you'll need to add the following
gems to your Gemfile:
<span class="n">gem</span> <span class="s1">'grape'</span><span class="n">gem</span> <span class="s1">'grape-swagger'</span>
In this example application, I decided to use grape-entity
to serialize models
but you can simply use their basic JSON representation.
Create some models with their API.
Let's create two models, Band
and Member
: 0aec804.
And their REST API using Grape: e2b15cb
Generate swagger specification file
As I mentioned in the previous section, there is many tools to generate the
swagger specification file of our API. I will use
Grape-swagger to generate
this file directly from the Grape code.
All you need to do is to add these lines to your api endpoint:
<span class="nb">require</span> <span class="s1">'grape-swagger'</span><span class="k">class</span> <span class="nc">BaseApi</span> <span class="o"><</span> <span class="ss">Grape</span><span class="p">:</span><span class="ss">:API</span> <span class="c1"># [...]</span> <span class="n">add_swagger_documentation</span> <span class="n">mount_path</span><span class="p">:</span> <span class="s1">'doc.json'</span><span class="k">end</span>
At this point, you can access the specification at
api.grape-swagger.local/doc.json
. This is this file that will be read by
Swagger UI.
Install Swager UI
You can install Swagger UI in various way. There are gems on github that I have
not tested yet:
- https://github.com/d4be4st/swagger-ui_rails
- https://github.com/kendrikat/grape-swagger-ui
- https://github.com/damrbaby/swagger_ui_rails
I chose to install swagger-ui from bower
to be able to specify a specific branch and use the latest version from github.
First, configure bower to install its packages in vendor/assets/components
:
<span class="p">{</span> <span class="nt">"directory"</span><span class="p">:</span> <span class="s2">"vendor/assets/components"</span><span class="p">,</span> <span class="nt">"json"</span><span class="p">:</span> <span class="s2">"bower.json"</span><span class="p">}</span>
And add this dependencie to your bower.json
:
"swagger-ui": "git://github.com/wordnik/swagger-ui"
.
Then, run bower install
.
Configure Swagger UI
Create a DocController
to expose a custom page for our API documentation.
A specific layout for this controller is a good idea.
Only one route is useful, let's choose #index
, create a
app/views/doc/index.html.haml
file containing the Swagger UI tag container.
#swagger-ui-container.swagger-ui-wrap
Now, create the javascript and stylesheet assets for this page.
The stylesheet should require these two files from the Swagger UI library:
//= require swagger-ui/dist/css/highlight.default//= require swagger-ui/dist/css/screen
The javascript file is much more complicated as we have to instantiate the
Swagger UI ourselves:
<span class="c1">#= require swagger-ui/dist/lib/shred.bundle</span><span class="c1">#= require swagger-ui/dist/lib/jquery-1.8.0.min</span><span class="c1">#= require swagger-ui/dist/lib/jquery.slideto.min</span><span class="c1">#= require swagger-ui/dist/lib/jquery.wiggle.min</span><span class="c1">#= require swagger-ui/dist/lib/jquery.ba-bbq.min</span><span class="c1">#= require swagger-ui/dist/lib/handlebars-1.0.0</span><span class="c1">#= require swagger-ui/dist/lib/underscore-min</span><span class="c1">#= require swagger-ui/dist/lib/backbone-min</span><span class="c1">#= require swagger-ui/dist/lib/highlight.7.3.pack</span><span class="c1">#= require swagger-ui/dist/lib/swagger</span><span class="c1">#= require swagger-ui/dist/swagger-ui</span><span class="nx">$</span> <span class="nf">-></span> <span class="nv">swaggerUi = </span><span class="k">new</span> <span class="nx">SwaggerUi</span> <span class="nv">url: </span><span class="s">"http://api.grape-swagger.local/doc.json"</span> <span class="nv">dom_id: </span><span class="s">"swagger-ui-container"</span> <span class="nv">supportedSubmitMethods: </span><span class="p">[</span><span class="s">'get'</span><span class="p">,</span> <span class="s">'post'</span><span class="p">,</span> <span class="s">'put'</span><span class="p">,</span> <span class="s">'delete'</span><span class="p">]</span> <span class="nv">onComplete: </span><span class="nf">(swaggerApi, swaggerUi)-></span> <span class="k">if</span> <span class="nx">console</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span> <span class="s">"Loaded Swagger UI"</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span> <span class="nx">swaggerApi</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span> <span class="nx">swaggerUi</span> <span class="nx">$</span><span class="p">(</span><span class="s">'pre code'</span><span class="p">).</span><span class="nx">each</span> <span class="nf">(i, e)-></span> <span class="nx">hljs</span><span class="p">.</span><span class="nx">highlightBlock</span> <span class="nx">e</span> <span class="nv">onFailure: </span><span class="nf">(data)-></span> <span class="k">if</span> <span class="nx">console</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span> <span class="s">"Unable to Load Swagger UI"</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span> <span class="nx">data</span> <span class="nv">docExpansion: </span><span class="s">"list"</span> <span class="nx">swaggerUi</span><span class="p">.</span><span class="nx">load</span><span class="p">()</span>
Let's have a look at the options passed to Swagger UI:
url
: the API specification url.dom_id
: id of our html tag.supportedSubmitMethods
: List of RESTful methods to allow in the UI.onComplete
: Callback to execute after SwaggerUi loads successfullyonComplete
: Callback to execute after Swagger fails to load.docExpansion
: How to display the list of all methods at initialization.
'list' will expand all methods, 'none' will hide blocks by default.
CORS
At this point, you may have noticed that our interface doesn't work because of
CORS problems.
The API endpoint is not on the same domain that our interface, and all requests
are performed on the client side, so we must use rack-cors
on our API endpoint
to authorize this kind of requests.
First, add rack-cors
to your Gemfile. Then add the following to config.ru
:
<span class="nb">require</span> <span class="s1">'rack/cors'</span><span class="n">use</span> <span class="ss">Rack</span><span class="p">:</span><span class="ss">:Cors</span> <span class="k">do</span> <span class="n">allow</span> <span class="k">do</span> <span class="n">origins</span> <span class="s1">'*'</span> <span class="n">resource</span> <span class="s1">'*'</span><span class="p">,</span> <span class="ss">headers</span><span class="p">:</span> <span class="ss">:any</span><span class="p">,</span> <span class="nb">methods</span><span class="p">:</span> <span class="o">[</span><span class="ss">:get</span><span class="p">,</span> <span class="ss">:post</span><span class="p">,</span> <span class="ss">:put</span><span class="p">,</span> <span class="ss">:delete</span><span class="p">,</span> <span class="ss">:options</span><span class="o">]</span> <span class="k">end</span><span class="k">end</span>
Restart your rails application, and all should be working like a charm.
Basic Authentication and CORS issue
In many projects, development applications are protected by Basic Authentication.
I needed to add this directive to my virtual host configuration to make
Swagger UI dealing well with an API behind an htaccess and on another domain :
if ($request_method = OPTIONS ) { add_header Content-Length 0; add_header Content-Type text/plain; add_header Access-Control-Allow-Origin *; add_header Access-Control-Allow-Headers 'origin, authorization'; add_header Access-Control-Allow-Methods 'GET, POST, PUT, DELETE, OPTIONS'; return 200;}
Then add your credentials to the Swagger UI authorization system:
# […] Swagger UI instantiation window.authorizations.add( "Basic", new PasswordAuthorization("basic_auth", 'hello', 'world') ) swaggerUi.load()
Success! All requests will contain the Authorization:Basic xxx
header,
and OPTIONS requests made to discover API succeed.
Convinced?
Now, when someone asks you "Is your API documentation up to date?"
You can say "YES!" with a huge smile… and go back playing table football ;)