Scattered
For a large project, writing an API, creating a Document for the project is not a simple task. In the case of continuous requirement updates from customers, it becomes more and more time consuming.
But without constant updates, it will be very difficult for other developers to use our API, as well as difficult to test the API.
Currently there are many open source tools to help programmers can write clear documents and can test the endpoint directly without using tools like postman to test API. Current variables are Swagger and API Blue Print.
In this article, I would like to introduce a tool that is SWAGGER, it helps us to write restfull standard documents easily for our project. And please note that it supports many languages, not just Ruby.
Deployment
I will write API with Ruby on Rails language
1. Build the API
You can refer to the document: http://apionrails.icalialabs.com/book/ to create your own Project. Or you can quickly create a rails project to test as follows:
1 2 3 4 5 6 7 8 | rails new swagger-rails cd swagger-rails rails g model User name:string email:string age:integer rails db:migrate |
Then add the following 2 gems to Gemfile:
1 2 3 | gem "swagger-block" gem "swagger-ui-engine" |
then run bundle install .
2. Document for API
2.1 What is Swagger?
- Is an open source toolkit for building OpenAPI specifications that enables us to design, build documentation, and use REST APIs.
- Swagger provides 3 main tools for developers:
Swagger-Editor is used to design new APIs or edit existing APIs via a config file.
Swagger-Codegen is used to generate code from existing config files.
Swagger-UI is used to generate html, css, etc. files from a config file.
- Writing documents for Swagger has two main approaches:
Top-down approach : designing APIs before coding.
Bottom-up approach : from the existing API design config files to describe them
- To make it easier to understand, you can access the demo link with Swagger UI http://petstore.swagger.io/ .
- With each API we can know the input and output details as well as which fields are required to submit, and what status the results can receive. In particular, we can input data to test the results.
2.2 Install Swagger UI
- Config wagger block:
Each doc will go together in the form of 1 action – 1 controller Swagger Block provides a method: Swagger :: Blocks.build_root_json is responsible for accepting classes that have no API information.
If following the instructions of gem swagger-block , we can configure the controller, but the problem is that the controller will be very big if placed doc there.
In addition, if you already know about the MVC pattern, or the SOLID principle, configuring the Controller is not recommended. Therefore, I will create a swagger folder to store the files related to swagger requests
123456789101112131415161718192021222324252627282930313233343536373839404142<span class="token comment"># app/controllers/concerns/swagger/users_api.rb</span><span class="token keyword">module</span> <span class="token constant">Swagger</span> <span class="token punctuation">:</span> <span class="token punctuation">:</span> <span class="token constant">UsersApi</span>extend <span class="token constant">ActiveSupport</span> <span class="token punctuation">:</span> <span class="token punctuation">:</span> <span class="token constant">Concern</span>include <span class="token constant">Swagger</span> <span class="token punctuation">:</span> <span class="token punctuation">:</span> <span class="token constant">Blocks</span>included <span class="token keyword">do</span>swagger_path <span class="token string">'/users/{id}'</span> <span class="token keyword">do</span> <span class="token comment">#link path request to server</span>operation <span class="token symbol">:get</span> <span class="token keyword">do</span> <span class="token comment"># method get</span>key <span class="token symbol">:summary</span> <span class="token punctuation">,</span> <span class="token string">'Find User by ID'</span> <span class="token comment"># summary</span>key <span class="token symbol">:description</span> <span class="token punctuation">,</span> <span class="token string">'Returns a single user if the user has access'</span> <span class="token comment"># description</span><span class="token comment"># định nghĩa params</span>parameter <span class="token keyword">do</span>key <span class="token symbol">:name</span> <span class="token punctuation">,</span> <span class="token symbol">:id</span> <span class="token comment"># name hiển thị là id</span>key <span class="token symbol">:in</span> <span class="token punctuation">,</span> <span class="token symbol">:path</span> <span class="token comment"># phương thức add params vào path. VD: lúc request lên sẽ là users/1</span>key <span class="token symbol">:description</span> <span class="token punctuation">,</span> <span class="token string">'ID of user to fetch'</span>key <span class="token symbol">:required</span> <span class="token punctuation">,</span> <span class="token keyword">true</span> <span class="token comment"># validate</span>key <span class="token symbol">:type</span> <span class="token punctuation">,</span> <span class="token symbol">:integer</span> <span class="token comment"># type params</span>key <span class="token symbol">:format</span> <span class="token punctuation">,</span> <span class="token symbol">:int64</span> <span class="token comment"># format params</span><span class="token keyword">end</span>response <span class="token number">200</span> <span class="token keyword">do</span><span class="token comment"># when get user succes</span>key <span class="token symbol">:description</span> <span class="token punctuation">,</span> <span class="token string">'user response'</span>schema <span class="token keyword">do</span>key <span class="token punctuation">:</span> <span class="token string">'$ref'</span> <span class="token punctuation">,</span> <span class="token symbol">:user_info</span><span class="token keyword">end</span><span class="token keyword">end</span>response <span class="token symbol">:bad_request</span> <span class="token keyword">do</span><span class="token comment"># when get user failed</span>key <span class="token symbol">:description</span> <span class="token punctuation">,</span> <span class="token string">'unexpected error'</span>schema <span class="token keyword">do</span>key <span class="token punctuation">:</span> <span class="token string">'$ref'</span> <span class="token punctuation">,</span> <span class="token symbol">:ErrorModel</span><span class="token keyword">end</span><span class="token keyword">end</span><span class="token keyword">end</span><span class="token comment"># ...</span><span class="token keyword">end</span><span class="token keyword">end</span><span class="token keyword">end</span>Then include it into the controller :
12345678<span class="token comment"># app/controllers/users_controller.rb</span><span class="token keyword">class</span> <span class="token class-name">UsersController</span> <span class="token operator"><</span> <span class="token constant">ApplicationControllers</span>include <span class="token constant">Swagger</span> <span class="token punctuation">:</span> <span class="token punctuation">:</span> <span class="token constant">UsersApi</span><span class="token comment"># ...</span><span class="token keyword">end</span> - Response and Parameters:
Response and parameters are very common in APIs. For example, after API login, you will have to return the User information. In the API show / edit user information, you will also have to return the User information, etc. We will have to find a way to reuse the code.
- Response :
In SwaggerUI, we can use $ ref to call a schema that has already been defined. Imagine it as a link to point to another JSON Schema.
12345678910111213141516171819<span class="token comment"># app/models/concerns/swagger/user_schema.rb</span><span class="token keyword">module</span> <span class="token constant">Swagger</span> <span class="token punctuation">:</span> <span class="token punctuation">:</span> <span class="token constant">UserSchema</span>extend <span class="token constant">ActiveSupport</span> <span class="token punctuation">:</span> <span class="token punctuation">:</span> <span class="token constant">Concern</span>include <span class="token constant">Swagger</span> <span class="token punctuation">:</span> <span class="token punctuation">:</span> <span class="token constant">Blocks</span>included <span class="token keyword">do</span>swagger_schema <span class="token symbol">:User</span> <span class="token keyword">do</span>key <span class="token symbol">:required</span> <span class="token punctuation">,</span> <span class="token punctuation">[</span> <span class="token symbol">:name</span> <span class="token punctuation">,</span> <span class="token symbol">:email</span> <span class="token punctuation">]</span>property <span class="token symbol">:name</span> <span class="token keyword">do</span>key <span class="token symbol">:type</span> <span class="token punctuation">,</span> <span class="token symbol">:string</span><span class="token keyword">end</span>property <span class="token symbol">:email</span> <span class="token keyword">do</span>key <span class="token symbol">:type</span> <span class="token punctuation">,</span> <span class="token symbol">:string</span><span class="token keyword">end</span><span class="token keyword">end</span><span class="token keyword">end</span><span class="token keyword">end</span>Then you need to include it in model user.rb
123456<span class="token comment"># app/models/user.rb</span><span class="token keyword">class</span> <span class="token class-name">User</span> <span class="token operator"><</span> <span class="token constant">ApplicationRecord</span>include <span class="token constant">Swagger</span> <span class="token punctuation">:</span> <span class="token punctuation">:</span> <span class="token constant">UserSchema</span><span class="token keyword">end</span>In addition, normally we will have 1schema ErrorSchema – it will be used when the API returns error information.
12345678910111213141516171819<span class="token comment"># app/models/concerns/swagger/error_schema.rb</span><span class="token keyword">module</span> <span class="token constant">Swagger</span> <span class="token punctuation">:</span> <span class="token punctuation">:</span> <span class="token constant">ErrorSchema</span>extend <span class="token constant">ActiveSupport</span> <span class="token punctuation">:</span> <span class="token punctuation">:</span> <span class="token constant">Concern</span>include <span class="token constant">Swagger</span> <span class="token punctuation">:</span> <span class="token punctuation">:</span> <span class="token constant">Blocks</span>included <span class="token keyword">do</span>swagger_schema <span class="token symbol">:ErrorOutput</span> <span class="token keyword">do</span>key <span class="token symbol">:required</span> <span class="token punctuation">,</span> <span class="token punctuation">[</span> <span class="token symbol">:errors</span> <span class="token punctuation">]</span>property <span class="token symbol">:errors</span> <span class="token keyword">do</span>key <span class="token symbol">:type</span> <span class="token punctuation">,</span> <span class="token symbol">:array</span>items <span class="token keyword">do</span>key <span class="token symbol">:type</span> <span class="token punctuation">,</span> <span class="token symbol">:string</span><span class="token keyword">end</span><span class="token keyword">end</span><span class="token keyword">end</span><span class="token keyword">end</span><span class="token keyword">end</span>Next, we need to create the common_response file to use the above schema
123456789101112131415<span class="token keyword">module</span> <span class="token constant">Swagger</span> <span class="token punctuation">:</span> <span class="token punctuation">:</span> <span class="token constant">ErrorResponses</span><span class="token keyword">module</span> <span class="token constant">NotFoundError</span><span class="token keyword">def</span> <span class="token keyword">self</span> <span class="token punctuation">.</span> <span class="token function">extended</span> <span class="token punctuation">(</span> base <span class="token punctuation">)</span>base <span class="token punctuation">.</span> response <span class="token number">404</span> <span class="token keyword">do</span>key <span class="token symbol">:description</span> <span class="token punctuation">,</span> <span class="token string">"404 - Resource not found"</span>schema <span class="token keyword">do</span>key <span class="token punctuation">:</span> <span class="token string">'$ref'</span> <span class="token punctuation">,</span> <span class="token symbol">:ErrorOutput</span><span class="token keyword">end</span><span class="token keyword">end</span><span class="token keyword">end</span><span class="token keyword">end</span><span class="token comment"># ...</span><span class="token keyword">end</span>
- Parameters :
Just like the Response part, the Parameter will also be shared by many APIs, so we will also conduct a separate definition of these parameters. For example token authentication or fields id
1234567891011121314<span class="token comment"># app/controllers/concerns/swagger/parameters.rb</span><span class="token keyword">module</span> <span class="token constant">Swagger</span> <span class="token punctuation">:</span> <span class="token punctuation">:</span> <span class="token constant">Parameters</span><span class="token keyword">def</span> <span class="token keyword">self</span> <span class="token punctuation">.</span> <span class="token function">extended</span> <span class="token punctuation">(</span> base <span class="token punctuation">)</span>base <span class="token punctuation">.</span> parameter <span class="token symbol">:user_id</span> <span class="token keyword">do</span>key <span class="token symbol">:in</span> <span class="token punctuation">,</span> <span class="token symbol">:path</span>key <span class="token symbol">:description</span> <span class="token punctuation">,</span> <span class="token string">'User ID'</span>key <span class="token symbol">:required</span> <span class="token punctuation">,</span> <span class="token keyword">true</span>key <span class="token symbol">:type</span> <span class="token punctuation">,</span> <span class="token symbol">:integer</span>key <span class="token symbol">:format</span> <span class="token punctuation">,</span> <span class="token symbol">:int64</span><span class="token keyword">end</span><span class="token keyword">end</span><span class="token keyword">end</span>Next, we declare it into the root file of swagger
123456789101112131415<span class="token comment"># app/controllers/concerns/swagger/sample_app_root.rb</span><span class="token keyword">module</span> <span class="token constant">Swagger</span> <span class="token punctuation">:</span> <span class="token punctuation">:</span> <span class="token constant">SampleAppRoot</span>extend <span class="token constant">ActiveSupport</span> <span class="token punctuation">:</span> <span class="token punctuation">:</span> <span class="token constant">Concern</span>include <span class="token constant">Swagger</span> <span class="token punctuation">:</span> <span class="token punctuation">:</span> <span class="token constant">Blocks</span>included <span class="token keyword">do</span>swagger_root <span class="token keyword">do</span><span class="token comment"># ...</span>extend <span class="token constant">Swagger</span> <span class="token punctuation">:</span> <span class="token punctuation">:</span> <span class="token constant">Parameters</span><span class="token keyword">end</span><span class="token keyword">end</span><span class="token keyword">end</span>Finally, the installation is complete, all that’s left now is to use swagger into the API
123456789101112131415161718192021222324252627282930<span class="token comment"># app/controllers/concerns/swagger/users_api.rb</span><span class="token keyword">module</span> <span class="token constant">Swagger</span> <span class="token punctuation">:</span> <span class="token punctuation">:</span> <span class="token constant">UsersApi</span>extend <span class="token constant">ActiveSupport</span> <span class="token punctuation">:</span> <span class="token punctuation">:</span> <span class="token constant">Concern</span>include <span class="token constant">Swagger</span> <span class="token punctuation">:</span> <span class="token punctuation">:</span> <span class="token constant">Blocks</span>included <span class="token keyword">do</span>include <span class="token constant">Swagger</span> <span class="token punctuation">:</span> <span class="token punctuation">:</span> <span class="token constant">ErrorSchema</span>swagger_path <span class="token string">"/ users / {id}"</span> <span class="token keyword">do</span>operation <span class="token symbol">:get</span> <span class="token keyword">do</span>key <span class="token symbol">:description</span> <span class="token punctuation">,</span> <span class="token string">"Returns the specified user"</span>key <span class="token symbol">:operationId</span> <span class="token punctuation">,</span> <span class="token symbol">:find_user_by_id</span>parameters <span class="token symbol">:user_id</span> <span class="token comment"># Include parameters defined by root</span>response <span class="token number">200</span> <span class="token keyword">do</span>key <span class="token symbol">:description</span> <span class="token punctuation">,</span> <span class="token string">"User specified by its ID"</span>schema <span class="token keyword">do</span>key <span class="token punctuation">:</span> <span class="token string">"$ref"</span> <span class="token punctuation">,</span> <span class="token symbol">:User</span><span class="token keyword">end</span><span class="token keyword">end</span><span class="token comment"># Captures the error response which is separately defined </span>extend <span class="token constant">Swagger</span> <span class="token punctuation">:</span> <span class="token punctuation">:</span> <span class="token constant">ErrorResponses</span> <span class="token punctuation">:</span> <span class="token punctuation">:</span> <span class="token constant">NotFoundError</span><span class="token keyword">end</span><span class="token keyword">end</span><span class="token keyword">end</span><span class="token keyword">end</span>Now, let’s look back at our project structure:
- Response :
2.3 Setup Swagger UI
The result above, we have 1 json file full information already. The task now is just how to show it to the client for easy viewing. We will use gem “swagger_ui_engine”
Perform the gem add on the gem file, run bundle and finally the configuration step of it in the config / routes.rb file
1 2 | mount <span class="token constant">SwaggerUiEngine</span> <span class="token punctuation">:</span> <span class="token punctuation">:</span> <span class="token constant">Engine</span> <span class="token punctuation">,</span> at <span class="token punctuation">:</span> <span class="token string">"/api_docs"</span> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | <span class="token comment"># config/initialize/swagger_ui_engine.rb</span> <span class="token keyword">if</span> <span class="token constant">Rails</span> <span class="token punctuation">.</span> env <span class="token punctuation">.</span> development <span class="token operator">?</span> <span class="token operator">||</span> <span class="token constant">Rails</span> <span class="token punctuation">.</span> env <span class="token punctuation">.</span> staging <span class="token operator">?</span> <span class="token constant">SwaggerUiEngine</span> <span class="token punctuation">.</span> configure <span class="token keyword">do</span> <span class="token operator">|</span> config <span class="token operator">|</span> config <span class="token punctuation">.</span> swagger_url <span class="token operator">=</span> <span class="token punctuation">{</span> v1 <span class="token punctuation">:</span> <span class="token string">"/api/v1/api_doc"</span> <span class="token punctuation">}</span> config <span class="token punctuation">.</span> validator_enabled <span class="token operator">=</span> <span class="token keyword">true</span> config <span class="token punctuation">.</span> json_editor <span class="token operator">=</span> <span class="token keyword">true</span> config <span class="token punctuation">.</span> request_headers <span class="token operator">=</span> <span class="token keyword">true</span> <span class="token keyword">end</span> <span class="token keyword">end</span> |
Now, we start the server and access the link: / api_docs, you will see your result.