DEV Community

Mathieu Ferment
Mathieu Ferment

Posted on

Use Swagger to document a Symfony API

Web APIs are trending !

They are now everywhere : they fuel Javascript single-app pages with data, they allow developers to manage an elasticsearch instance, they are used to decouple monolithic applications into micro-services ...

And because they are widely used in modern web apps, we can see that the tooling for APIs has upgraded a lot too. Automated API documentation systems, API client SDKs, REST user-friendly clients ...

I wanted to share some cool tool combinations tricks I have learned this year.
This post will show you how you can combine some tools together in order to build, for a php API:

  • an accurate HTML documentation using Swagger
  • a PHP API Client SDK using Jane
  • API tests using Behat

I split the post in 2 parts because it was quite long to read.

The API

I use in this example a simple Symfony REST API to handle a Movies database.
It has 3 endpoints:

  • GET /v1/movies to fetch movies from the database, which accepts some parameters: dir (sorting direction, "asc" or "desc"), page (for pagination) and sort (to select which field to sort on)
  • POST /v1/movie to register a new movie into the database
  • DELETE /v1/movie/{id} to delete an existing movie from the database

The DELETE endpoint is a soft delete: the record is marked as deleted but is not truly deleted.

The API was built using Symfony 3.1, a mysql database handled with DoctrineORM and the library FOSRestBundle. Nothing unusual here, Symfony developers will recognize something very standard:

You can have a look at the code here.

Swagger

Here comes the fun. The API is built and tested, now I would like other developers to use it (for example to build a cool JS frontend).

These developers will ask me "where's the doc ?" because they cannot guess how the API works.

I don't want to write this documentation, because I am a developer so I love to automate everything (especially boring tasks) and also because I do not want to update it everytime I modify the API. That is why I want it to be generated from the code.

If I am able to generate the documentation from my code, whenever I modify the code the documentation will be updated too. No more out-of-date doc, no more mispelled parameters.

There is multiple ways to build an automated HTML API documentation. For Symfony apps, the official documentation redirects to NelmioApiDocBundle which is easy to use and efficient. It parses some of your app config and asks you to annotate what cannot be guessed. It generates then a great HTML documentation such as this one:

I chose not to use it and instead to generate a Swagger file for my API. Swagger is, among other things, a standard API JSON specification format that defines how to describe an API in a JSON file. What's cool about it is that this format is supported by a lot of API tools, so having a Swagger file about your API allows you to use all of these tools easily.

For example,

  • SwaggerUI allows developers to visualize and interact with your API based on your Swagger file
  • StopLight.io can build a documentation portal from your Swagger file
  • Postman can use Swagger files to generate requests bodies and templates to make it easy for you to consume an API

Annotate the API

So I needed to produce a Swagger file which described my API. I first tried to use the Swagger option of NelmioApiDocBundle which allows to dump the API description as a Swagger file. However the file produced was using Swagger v1.2 specification while the tools I would use later in this post required a Swagger v2 file.

There are tools such as api-spec-converter to convert a Swagger v1 file into a Swagger v2 file but I had a a lot of issues because of this conversion so I decided to go for a v2 file directly.

I used the swagger-php project to add annotations to my Symfony API which can then be parsed to produce the wanted file. Basically you need to annotate your Controllers and the Models being used (request bodies and response bodies).

For example here are the annotations needed to describe the GET endpoint:

    /**
     * @SWG\Get(
     *     path="/movies",
     *     summary="Get movies",
     *     description="Get movies",
     *     operationId="getMovies",
     *     produces={"application/json"},
     *     @SWG\Parameter(
     *         name="order",
     *         in="query",
     *         description="Order criterion",
     *         type="string",
     *     ),
     *     @SWG\Parameter(
     *         name="dir",
     *         in="query",
     *         description="Sort criterion",
     *         type="string",
     *     ),
     *     @SWG\Parameter(
     *         name="page",
     *         in="query",
     *         description="Page number",
     *         type="integer",
     *     ),
     *     @SWG\Response(
     *         response=200,
     *         description="Success",
     *         @SWG\Schema(ref="#/definitions/MoviesViewDTO"),
     *     )
     * )
     */
    public function getMoviesAction(Request $request)
Enter fullscreen mode Exit fullscreen mode

The syntax is quite straight-forward, you have to describe what your endpoint accepts as parameters / request bodies and what it returns.

About the request and responses bodies, Swagger-php is able to analyze PHP models to extract their structure if provided with some annotations:

/**
 * @SWG\Definition()
 */
class MoviesViewDTO
{
    /**
     * @var int
     *
     * @SWG\Property()
     */
    public $total;
    ...
Enter fullscreen mode Exit fullscreen mode

So if you accept and return DTOs (a.k.a POPOs), you can easily extract their structure to be written in the Swagger file.

Once you have annotated your routes and your models, you can run the swagger php binary on your project and it generates your Swagger file. It looks like this:

{
    "swagger": "2.0",
    "info": {
        "title": "Movies API",
        "version": "1.0"
    },
    "basePath": "/v1",
    "schemes": [
        "http"
    ],
    "paths": {
        "/movies": {
            "get": {
                "summary": "Get movies",
                "description": "Get movies",
                "operationId": "getMovies",
                "produces": [
                    "application/json"
                ],
                "parameters": [
                    {
                        "name": "order",
                        "in": "query",
                        "description": "Order criterion",
                        "type": "string"
                    },
                    {
                        "name": "dir",
                        "in": "query",
                        "description": "Sort criterion",
                        "type": "string"
                    },
                    ...
}
Enter fullscreen mode Exit fullscreen mode

The complete file is available on github.

Congratulations: the Swagger file is written !

Use the Swagger file

HTML documentation

Now that we got this file we can use swagger-editor to see what the HTML documentation looks like. We just dump the JSON content into the editor and it renders the HTML doc:

The doc can then be dumped and hosted on a webserver so it is available all the time for developers to look at it. And if you use SwaggerUI, you even are provided sandbox capabilities to perform real HTTP requests to see the API behave in realtime !

Use it with a REST client

We can also input it in Postman to obtain a pre-configured REST client to interact with the API:


The requests are preconfigured and now it is very easy to perform HTTP requests from the client. This is especially useful when onboarding a new developer on the team, so he gets his dev/debug tool ready in a few seconds !

Use it as a team collaboration tool

If your company have a backend team which works on an API and a frontend team which uses this API, they need to discuss and agree on the API behavior.

Using Swagger specification can be useful: when working on a new feature, the 2 teams work together on the Swagger file and agree that the updated file is what the API needs to be for the feature. Then the backend team starts working on the implementation of the Swagger specification, while the frontend team can start working because they know what the API will accept and return. Some tools can even generate mock APIs using a Swagger file in order to provide the frontend team a mock API to use until the backend team finishes its job.

Jane & Behat

See you in the 2nd part of this post :)

Top comments (8)

Collapse
 
plesiosaure profile image
plesiosaure

Hello,

This approach is really interesting. Thank you for sharing. I also encountered some problems with Nelmio 2 not compatible with symfony 3.4 (seems to use old templating system) but Nelmio 3 use @SWG annotations.
Nevertheless, if you are in my case and had already used annotations @Rest\Get and
@Rest\QueryParam to simplify the code in the controller thanks to FosRest, how to avoid doubling annotations to generate a doc ? any idea ? thanks

Collapse
 
matks profile image
Mathieu Ferment

I created a basic Symfony4 API using FOSRestBundle 2.3.1 and NelmioApiDocBundle 3.1. I confirm Nelmio was able to parse my FOS\RestBundle\Controller\Annotations\QueryParam annotation and generate the right doc.

My Controller:

<?php

namespace App\Controller;

use FOS\RestBundle\Request\ParamFetcher;
use FOS\RestBundle\Controller\Annotations\RequestParam;
use FOS\RestBundle\Controller\Annotations\QueryParam;
use FOS\RestBundle\Controller\Annotations\FileParam;
use Acme\FooBundle\Validation\Constraints\MyComplexConstraint;

use Symfony\Component\Routing\Annotation\Route;

class FooController
{
    /**
     * @QueryParam(name="page", requirements="\d+", default="1", description="Page of the overview.")
     * @Route("/api/rewards", methods={"GET"})
     * @param ParamFetcher $paramFetcher
     */
    public function aaa(ParamFetcher $paramFetcher)
    {
    }
}

The computed swagger file:

{"swagger":"2.0","info":{"title":"My App","description":"This is an awesome app!","version":"1.0.0"},"paths":{"\/api\/rewards":{"get":{"parameters":[{"name":"page","in":"query","allowEmptyValue":false,"required":false,"description":"Page of the overview.","type":"string","format":"\\d+","default":"1"}],"responses":{"default":{"description":""}}}},"\/api\/doc.json":{"get":{"responses":{"default":{"description":""}}}}}}
Collapse
 
matks profile image
Mathieu Ferment

Symfony3 documentation seems to suggest Nelmio 3 is able to parse FOSRest annotation, and to generate a Swagger JSON file accordingly.

This tutorial, in French unfortunately, says that it uses @Rest\QueryParam and that Nelmio is able to export it using as a Swagger file.

I will give it a try this weekend. If this does not work, we know that Nelmio can read FOSRest annotations and generate the right HTML doc from it. So I guess the best would be to make a PR to Nelmio project to allow this behavior.

Collapse
 
Sloan, the sloth mascot
Comment deleted
Collapse
 
matks profile image
Mathieu Ferment • Edited

The idea behind this post was to generate a Swagger file that can then be used for multiple purposes: generate a documentation, generate an API Client, generate pre-built query samples in Postman, be used to check if API responses are valid against the definition ...

Sami is a great tool but as far as I know only handles the documentation.

Collapse
 
guenneguezt profile image
guenneguezt

Hi,

I use anotate like :
* @SWG\Get(
* path="/agent/{ip<[.\d]>}/{port<\d>}/config",
....
When I do : bin/console debug:router
The route isn't list.
Only route define like following are detected :
* @route ("/agent/{ip
>}/{port<\d*>}/config", methods="GET")

Thanks for help
Thomas

Collapse
 
shubaivan profile image
shubaivan

Could you help me understand, how to run (get up) this project in local?

Collapse
 
api_patrol profile image
Pawel Duda

What about annotating controllers that are in "vendors" for the purpose of generatting Swagger docs. Let's say "FOSUserBundle" which is usaully not overridden in the project.