How do we manage our API documentations in Idearun Startup Studio

Hasan Noori
Formaloo
Published in
10 min readFeb 25, 2020

--

Over the past few years, we’ve been working on many different projects and products for Idearun startup studio. Most of our projects (all of the big ones), use an API based system, with an API backend handling all data and logic, and different API clients (web, mobile, bots, and etc.) handling user representations. Over time, aside from our own client developers, we’ve had to provide APIs for third party developers and also some public APIs. As any other API provider, we had to come up with a proper way to provide decent documentation on our endpoints, so that any developer can use them on their end without any trouble.

This is an article about our journey on providing good API documentation for our developers. From the day we had only a handful of developers, working on a couple of projects, to where we are now, we have had to provide several different APIs on various projects, to many developers around the world, using our APIs in their projects.

We use Python/Django to develop the back-ends of our projects. And we develop our APIs with Django Rest Framework (DRF). We also use a core library named Idealib for all of our projects. This library gathers all the core parts of our code, and their updates between different projects.

Please keep in mind that this article is more about software engineering than Django or DRF. So, we won’t focus on details such as our codes, and will mostly focus on stages of our growth, problems and requirements we faced on each step, and our approach to solve each.

Starting from zero

We had our first API based project, Techpin, with only two developers on the back-end and front-end. Documentation at this point was not too critical, since the two developers were working together most of the time, and if there was any lack of documentation, the ease of communication would cover up for it. So, at this point, we used a basic form of documentation, provided by DRF. An API schema, auto generated from the code and its comments, and represented in JSON format.

At this point, I should note that we have a set of basic rules for providing documentation in an agile team (we will cover it in another article), and we’ve stuck to them since day one. But some rules, related to this topic are as follows:

· Minimality: Documentations should be as minimal as possible. They should contain all the necessary data, but nothing more.

· Auto-generated over manually written: Use a system to extract the documents from the code, and its comments. Developers in most cases don’t like writing documents (no matter how much you try to convince them), but they will be more cooperative in writing better code.

· Don’t Repeat Yourself (or anyone else): Taking this concept from rules to write a better code, we’ve extended it to writing documents. If you have an existing document, use it instead of writing a new one. If you’re generating any useful data somewhere else (e.g. written tests), try to reuse them for your documentation.

Django rest framework does a great job in extracting the API documentation from code. It inspects all the endpoints, their accepted method, accepted inputs and their types, whether each parameter is required or optional, and so on. But more than this, DRF will make use of some of the comments and metadata (e.g. the “help text” written for a field), written on each endpoint, or each model field, and adds them to the API schema.

So, at first, by just writing a clean and well documented code on the backend, we would have a minimum API schema which would do the job for our first project.

Growing in size

Moving to our next project, many things changed. We had a much bigger project on our hand, with more developers working on them. Our developers couldn’t use as much communication as before, for not only the number had increased, but also since we had flexible working hours and remote working, developers couldn’t work as closely as before. On top of this, our new project had to provide an API for some third party developer. The JSON based schema was not going to cut it anymore!

At this point, we had to provide a more sophisticated representation for our API documents. Along with some data which couldn’t be extracted from the code, like our overall request and response structure, some descriptions on product modules, etc.

We tried to use Django rest swagger at this point. It would basically use the schema provided by Django rest framework, and build on it, adding some functionalities to provide examples and online API sandbox. But due to it having some problems and not supporting any extra documents (e.g. writing a page on our request/response) structure, we decided to write a simple solution ourselves.

Our solution was building a web interface on top of the JSON based schema. In the first step, our system would use a JSON provided by DRF, and represent it in a web interface, making it much more human readable. the next step, we added a simple CMS, allowing us to add documentation pages.

The new system worked well for some time, while gradually growing with minor updates such as adding search between documents, minor UI/UX improvements on the documentation, being able to write a documentation page on each individual endpoint (containing examples for the mentioned endpoint), etc.

Growing in project complexity

Eventually, as we worked on more complex projects, and more developers used our systems, new problems started to emerge. Basically, our documents lacked a set of examples, and our back-end developers didn’t have enough time to write them. At this point, front-end developers would contact the back-end team to ask questions on each endpoint, but as the team grew, it became more and more troublesome and time consuming for both sides. On top of that, lack of communication with third party developers, was becoming a great problem on their side.

Once again, we used Django rest swagger, alongside our main documentation system. But, after some time, we saw that no one’s using it for testing the endpoints. The problem with it was that the front-end developers needed examples for the complex endpoints, which relied on data generated from other endpoints, and Django rest swagger wouldn’t help much on this matter.

An example for these endpoints is as follows:

On an e-commerce platform, in order to make a purchase, users should sign up, add an address to their profile, add some products to their cart, and attempt to make the order. If someone wants to use this endpoint, they need to do all these steps, just to see how the response looks. And Django rest swagger couldn’t handle these cases very well.

Writing the examples manually would be the first solution that comes to mind, but that would have introduced a lot of problems by itself, as writing the documents is not only time consuming (and despised by most developers), but also creates more problems on updates. For example what if a field has changed on the documents? (keep in mind that a lot of these problems happen during the development of internal APIs, or when API endpoints are still prone to change.)

Our developers created a solution for the problem: Using Postman for writing and sharing the examples. Since all of our developers already used postman to test or inspect the endpoints, it was a common tool between them, and an easy way to share the data and examples.

The new solution not only solved the previous problem, but also added an extra layer of functional test for the endpoints. But it also had its own problems: First, sharing the postman examples with third party developers would have its own limitations, especially if you’re working on a public API. Second problem was that the idea of having our documents in two different places wasn’t very graceful.

To solve this problem, we created a simple solution: Transfer the examples from postman to our own schema. We wrote a script that went through a JSON file generated by postman, and added it to our API documentation. This way, our back-end developers could regularly update the documents with new and valid examples, without having to write any manual document.

Going public

This was not our final step yet. Another problem happened when we decided to provide public API for one of our main products. The main problem was that our existing system of documentation, would read all the existing endpoints from the DRF schema, and present them to developers. There were two main problems with this approach, when taken to a public API: First, it was inefficient. Each time someone opened the API documentation page, the system would automatically inspect all the endpoints and render them into html. It’s not much of a problem with a limited number of developers reading the documentation. But in a public API, this would be very inefficient. We needed to extract the data once, and save it in a database for later calls.

The second problem with our system was much bigger. We showed all of the endpoints to any authorized developer. But in reality, we may not want to expose all of our endpoints to the public, for various reasons. For example, we may not everyone to see our old and deprecated endpoints. Or we may want to limit the access to endpoints based on the plan a user has paid for.

We already had a solution on authorization and permission management level. Our system would inspect and organize our endpoints in groups, and we could limit each application’s access to specific endpoints or endpoint groups. We decided to use this approach to solve our new problems.

Our system inspects our code and updates or list of endpoints when our code is updated. Later we use the saved endpoints when serving our document, instead of inspecting the code each time. We also define different groups containing different sets of endpoints, and add them to our documents. We also can define different API documents, each containing a set of endpoints groups and pages, accessed by specific users and user groups. All of these give us great flexibility in creating and publishing our API documentation.

Using our tests

Finally, the last step we took to improve our API documents was using our automatic tests. We extended DRF’s test client with some functions, which would capture any test request, along with their response, and save them as examples for its respective endpoint. So, by just writing a code with proper documents, and proper tests, we have a sophisticated document with request/response examples.

Where are we now?

So, going through all these steps, our API documentations system has grown to cover all of our current needs, using the following specifications:

  • A set of different API documentations, accessed by users based on their access level and permissions.
  • Each API documentation contains its own pages (e.g. describing our API standards, or general request and response structure), and a set of endpoint groups.
  • Each endpoint group contains a set of endpoints, each containing full documents and examples.
  • Most of the documents are generated automatically from the code.
  • Our examples are all imported from our test and postman examples.
  • We can add any manual document, where and when needed.
  • We can organize our documents, and manage the endpoints in each.

What did we learn?

Finally, although our current system covers our needs on a good level, we know that we will need to upgrade our system over and over again in the future, for our needs and environment will change, and we will respond to each change as we have done in the past. For example, we may want to migrate into GraphQL in the future, and we will have to create a new system to support it.

These are some lessons we learnt developing our API documentation system:

  • Avoid perfectionism, always use the minimal system fit to your needs.
  • Solve each problem at the right time. Focus on finding/predicting any problem, and responding to them as quickly, effectively, and efficiently as possible, but avoid solving problems you don’t and won’t have.
  • Stay close to the developers, for most of the time, they know most of the problems and the solution related to their work.
  • Keep everything agile, whether it’s your solutions, documentation, etc.
  • Never repeat any process or data. For example, if any data is produced by a system, try to find an efficient way to reuse them.
  • Always look for existing solutions and their strengths and weaknesses. (And here I should admit that by doing this more carefully, I would avoid a lot of trouble!).
  • If not spoken, a lot of problems would go unnoticed. Provide the necessary means for your team to communicate with each other about the current problems and shortcomings, and possible ways to solve them.

--

--

Hasan Noori
Formaloo

Co-founder and CTO of Formaloo | Part-time Geek | Philosophy lover