Creating an API documentation in Swagger using YAML

UPD: I wrote this for swagger v1.2. Stuff changed since then. Now swagger allows you using yaml and it wants just one file so making specs is easier now. What a time to be alive!

Few days ago was the first time I created a REST API documentation. This is the report how I did it.

There are several generators that generate pretty interface for your documentation so you don't have to do it by hands. I've been picking between two variants:

  • API Blueprint - it allows you to write API documentation in markdown and there is a service apiary that generates focumentation from it for free. Also three are several useful tools that work with this format: they generate server mocks test your APIs and do other cool stuff.

  • Swagger - wants you to write documentations in json-format. It has a cool dеpendency-free documentation interface that creates pages like this. Also it has a lot of client code generators for different languages.

First I wanted to use blueprint because of markdown. Yes, swagger's json files looks ugly. But then I decided that it's important to store documentation inside the project. It allows you to have different documentations for different api versions (like dev and prod). And it increases the chance that your docs won't be forgotten.

It's cool to generate documentation by reading your application docblocks but there's a lot of metadata that I don't know where to store. I wanted to create documentation manually at least once to know what to automate. But if you want to generate it from docblock - there's a library Swagger-PHP that makes it for you.

First we'll create an interactive documentation. You need to download Swagger UI, move directory "dist" to your public web folder and rename it to "docs". Congratulations, it's already working. It was not so hard. But for now it shows documentation for petstore. You need to edit file "index.html" and change url to your json api docs url. There's a convention to store them in "api-docs" directory.

Now about creating json docs. Here's the specification for them. And you don't want to create json by hands. There's a human readable format to store data scructures like this. It's called YAML. It's a lot more convinient to write data in YAMl than in JSON. I used symfony/Yaml package and created an action that reads YAML files and converts them to JSON on the fly.

There's one more YAML feature that can help you a lot - aliases. When you're using a hash or an array for the first time you can name it and then you need to use it again you can white it's name and not to copy-paste it's content. Documentation has a lot of recurring blocks so aliases are very handful in this case.

But there's a problem with aliases. Specification wants you to declare each API group in separate file and you can't use aliases declared in another files. And there's a lot of data parts that are used in all files (like error responce models).

I created a file _includes.yml that contains all common aliases and end with an empty line. All it's contents are grouped in a hash called _includes. I add this file text to beginning of every yaml file before parsing YAML and remove group _includes before converting to json. This allows us to use global aliases. YAML validators fire an error when see using of alias that is not declared in current file so we have to write %alias instead of *alias in YAML files and to convert it using preg_replace before parsing yaml. I have no words starting with "%" char in my documentation so it's ok.

Here's the yii routing rules for my documentation:

'api-docs' => 'apiDocs/index',
'api-docs/<view>' => 'apiDocs/index',

And here's the controller I used:

use Symfony\Component\Yaml\Yaml;

/**
 * This controller converts yaml docs files to json format for swagger-ui.
 */
class ApiDocsController extends ApiController
{
    public function actionIndex($view = 'index')
    {
        $file = Yii::getPathOfAlias('application.docs.' . $view) . '.yml';
        if (!file_exists($file))
            throw new CHttpException(404, 'File not found');

        $yaml =
            file_get_contents(Yii::getPathOfAlias('application.docs._includes') . '.yml') .
            file_get_contents($file);

        // Replace placeholders
        $yaml = strtr($yaml, [
            '%apiv%' => '0.1.0',
            '%host%' => Yii::app()->request->hostInfo,
        ]);

        // Convert remote alias calls
        $yaml = preg_replace('~%([a-z+])~i', '*$1', $yaml);

        $data = Yaml::parse($yaml);
        unset($data['_includes']);
        echo json_encode($data);
    }
}

That's how I created an API coumentation.

Categories: Howtos

Tags: , , , , , , ,