Hey everyone!
So, recently I tackled a really interesting challenge: implementing API versioning for one of our projects and making sure the documentation stayed crystal clear. We decided to go with Swagger (now OpenAPI Specification) for the docs, and I wanted to share the steps we took to bring V2 of our API to life alongside its shiny new documentation.
Step 1: Laying the Foundation for V2 Controllers
First things first, we needed a dedicated space for our V2 controllers. It felt nice and organized to create a new directory structure. Here's a peek at how we set it up in our Ruby on Rails app:
# app/controllers/api/v2/users/users_controller.rb
module Api
module V2
module Users
class UsersController < ApplicationController
# Our awesome V2 controller methods will live here!
end
end
end
end
This keeps things nice and tidy, separating the concerns of different API versions.
Step 2: Telling Swagger About V2
Next up, we needed to tell Swagger about our new V2. This involved tweaking our swagger_helper.rb
file. We essentially added a whole new section to define the documentation specifically for V2:
# spec/swagger_helper.rb
RSpec.configure do |config|
config.swagger_docs = {
'v1/swagger.json' => {
# Our existing V1 configuration stayed here
},
'v2/swagger.json' => {
openapi: '3.0.1',
info: {
title: 'API V2',
version: 'v2',
description: 'User service V2 APIs'
},
paths: {},
servers: [
{
url: '{defaultHost}',
variables: {
defaultHost: {
default: 'http://localhost:3000'
}
}
}
],
components: {
securitySchemes: {
# Our security setup...
},
schemas: {
# Defining our data structures...
}
}
}
}
end
Notice how we defined a separate entry for 'v2/swagger.json'
with its own info section, clearly marking it as V2.
Step 3: Writing Specs That Know Their Version
To make sure our V2 endpoints were properly documented, we created new request spec files specifically for them. The key here was linking these specs to the V2 Swagger documentation:
# spec/requests/api/v2/users_spec.rb
require 'swagger_helper'
RSpec.describe 'api/v2/users', swagger_doc: 'v2/swagger.json', type: :request do
path '/api/v2/users/send_otp' do
patch('send otp') do
# All the details about this V2 endpoint go here!
end
end
end
That swagger_doc: 'v2/swagger.json'
line is the magic that tells RSwag to include this documentation in our V2 Swagger file.
Step 4: Giving Users a Choice on the Docs Page
Okay, so we had the V2 documentation all set up behind the scenes. But what good is it if you can't actually see it alongside the original? We needed to give users a way to easily switch between API versions right there on the documentation page. My initial thought was to configure the RSwag UI to list both V1 and V2 endpoints. Here's what I added to our config/initializers/rswag_ui.rb:
Done the below thing in your Rails app
# config/initializers/rswag_ui.rb
Rswag::Ui.configure do |c|
c.openapi_endpoint "/api-docs/v1/swagger.json", "API V1 Docs"
c.openapi_endpoint "/api-docs/v2/swagger.json", "API V2 Docs"
# Add Basic Auth in case your API is private
# c.basic_auth_enabled = true
# c.basic_auth_credentials 'username', 'password'
end
In theory, this should have presented a nice little dropdown or list in the UI, allowing seamless navigation between API V1 and V2. But... spoiler alert... it didn't work right away! Cue the head-scratching and mild frustration. I knew I must have missed something obvious, and I made a mental note to revisit this mystery at the end of the post.
This should have given a clear and intuitive way to navigate between the different API versions in the UI.
Step 5: Unleashing the Swagger Rake Task!
With all the configurations in place, it was time to generate the actual Swagger documentation files. This was as simple as running this Rake task:
bundle exec rake rswag:specs:swaggerize
OR
rails rswag
This command did its thing and, lo and behold, we had both swagger/v1/swagger.json
and swagger/v2/swagger.json
ready to go!
Step 6: The Importance of Testing Across Versions
Of course, no development process is perfect, and we did hit a few snags along the way:
Ensuring V1 Still Plays Nice: We had to double-check that any underlying V1 methods we reused in V2 were still working as expected and didn't have any unintended side effects.
Key Takeaways from the Journey
Here are a few things that really stood out during this process:
- Keep Schemas Separate: Maintaining distinct schemas for each API version in swagger_helper.rb is key for clarity and avoids confusion. Be Explicit with swagger_doc: Using the swagger_doc parameter in your request specs is essential for linking the documentation to the correct version.
- Embrace Component Reuse (Where Sensible): If there are common data structures or security schemes, reusing them across versions can save time and effort, but be mindful of potential version-specific changes.
- Test, Test, Test Across Versions: I can't stress this enough! Thorough testing of both old and new API versions is vital for a smooth transition and to prevent breaking existing functionality.
The thing I missed is very funny. Since we change the initializers file, we need to restart the server. I totally missed that and spent some additional time scratching my head! Later found my own silly mistake and fixed it 😂. Always the simple things, right?
Overall, implementing API versioning and documenting it with Swagger was a worthwhile effort. It provides a much cleaner and more organized way to evolve our API while keeping our developers (and anyone integrating with us) well-informed.
What are your experiences with API versioning and documentation? I'd love to hear your thoughts and any tips you might have! 😊