v2.3 Swagger / Open API Support

API Platform natively supports the Open API (formerly Swagger) API specification format. It also integrates a customized version of Swagger UI, a nice tool to display the API documentation in a user friendly way.

Screenshot

# Overriding the Swagger Documentation

Symfony allows to decorate services, here we need to decorate api_platform.swagger.normalizer.documentation.

In the following example, we will see how to override the title of the Swagger documentation and add a custom filter for the GET operation of /foos path

# api/config/services.yaml
services:
    'App\Swagger\SwaggerDecorator':
        decorates: 'api_platform.swagger.normalizer.api_gateway'
        arguments: [ '@App\Swagger\SwaggerDecorator.inner' ]
        autoconfigure: false
<?php
// api/src/Swagger/SwaggerDecorator.php

namespace App\Swagger;

use Symfony\Component\Serializer\Normalizer\NormalizerInterface;

final class SwaggerDecorator implements NormalizerInterface
{
    private $decorated;

    public function __construct(NormalizerInterface $decorated)
    {
        $this->decorated = $decorated;
    }

    public function normalize($object, $format = null, array $context = [])
    {
        $docs = $this->decorated->normalize($object, $format, $context);

        $customDefinition = [
            'name' => 'fields',
            'description' => 'Fields to remove of the output',
            'default' => 'id',
            'in' => 'query',
        ];


	// e.g. add a custom parameter
	$docs['paths']['/foos']['get']['parameters'][] = $customDefinition;
	
        // e.g. remove an existing parameter
        $docs['paths']['/foos']['get']['parameters'] = array_values(array_filter($docs['paths']['/foos']['get']['parameters'], function ($param){
            return $param['name'] !== 'bar';
        }));
	
	// Override title
	$docs['info']['title'] = 'My Api Foo';

        return $docs;
    }

    public function supportsNormalization($data, $format = null)
    {
        return $this->decorated->supportsNormalization($data, $format);
    }
}

# Using the Swagger Context

Sometimes you may want to change the information included in your Swagger documentation. The following configuration will give you total control over your Swagger definitions:

<?php
// api/src/Entity/Product.php

namespace App\Entity;

use ApiPlatform\Core\Annotation\ApiResource;
use ApiPlatform\Core\Annotation\ApiProperty;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;

/**
 * @ApiResource
 * @ORM\Entity
 */
class Product // The class name will be used to name exposed resources
{
    /**
     * @ORM\Column(type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    public $id;

    /**
     * @param string $name A name property - this description will be available in the API documentation too.
     *
     * @ORM\Column
     * @Assert\NotBlank
     *
     * @ApiProperty(
     *     attributes={
     *         "swagger_context"={
     *             "type"="string",
     *             "enum"={"one", "two"},
     *             "example"="one"
     *         }
     *     }
     * )
     */
    public $name;

    /**
     * @ORM\Column
     * @Assert\DateTime
     *
     * @ApiProperty(
     *     attributes={
     *         "swagger_context"={"type"="string", "format"="date-time"}
     *     }
     * )
     */
    public $timestamp;
}

Or in YAML:

# api/config/api_platform/resources.yaml
resources:
    App\Entity\Product:
      properties:
        name:
          attributes:
            swagger_context:
              type: string
              enum: ['one', 'two']
              example: one
        timestamp:
          attributes:
            swagger_context:
              type: string
              format: date-time

Will produce the following Swagger documentation:

{
  "swagger": "2.0",
  "basePath": "/",

  "definitions": {
    "Product": {
      "type": "object",
      "description": "This is a product.",
      "properties": {
        "id": {
          "type": "integer",
          "readOnly": true
        },
        "name": {
          "type": "string",
          "description": "This is a name.",
          "enum": ["one", "two"],
          "example": "one"
        },
        "timestamp": {
          "type": "string",
          "format": "date-time"
        }
      }
    }
  }
}

# Changing the Name of a Definition

API Platform generates a definition name based on the serializer groups defined in the (de)normalization_context. It’s possible to override the name thanks to the swagger_definition_name option:

/**
 * @ApiResource(
 *      collectionOperations={
 *          "post"={
 *              "denormalization_context"={
 *                  "groups"={"user:read"},
 *                  "swagger_definition_name": "Read",
 *              },
 *          },
 *      },
 * )
 */
class User
{
}

It’s also possible to re-use the (de)normalization_context:

/**
 * @ApiResource(
 *      collectionOperations={
 *          "post"={
 *              "denormalization_context"=User::API_WRITE,
 *          },
 *      },
 * )
 */
class User
{
    const API_WRITE = [
        'groups' => ['user:read'],
        'swagger_definition_name' => 'Read',
    ];
}

# Changing Operations in the Swagger Documentation

You also have full control over both built-in and custom operations documentation:

In Yaml:

resources:
  App\Entity\Rabbit:
    collectionOperations:
      create_user:
        method: get
        path: '/rabbit/rand'
        controller: App\Controller\RandomRabbit
        swagger_context:
          summary: Random rabbit picture
          description: >
            # Pop a great rabbit picture by color!

            ![A great rabbit](https://rabbit.org/graphics/fun/netbunnies/jellybean1-brennan1.jpg)            

          parameters:
            -
               in: body
               schema:
                   type: object
                   properties:
                       name: {type: string}
                       description: {type: string}
               example:
                   name: Rabbit
                   description: Pink rabbit

or with XML:

<?xml version="1.0" encoding="UTF-8" ?>
<resources xmlns="https://api-platform.com/schema/metadata"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="https://api-platform.com/schema/metadata
           https://api-platform.com/schema/metadata/metadata-2.0.xsd">
    <resource class="App\Entity\Rabbit">
        <collectionOperations>
            <collectionOperation name="create_user">
                <attribute name="method">get</attribute>
                <attribute name="path">/rabbit/rand</attribute>
                <attribute name="controller">App\Controller\RandomRabbit</attribute>
                <attribute name="swagger_context">
                    <attribute name="summary">Random rabbit picture</attribute>
                    <attribute name="description">
                        # Pop a great rabbit picture by color!

                        ![A great rabbit](https://rabbit.org/graphics/fun/netbunnies/jellybean1-brennan1.jpg)
                    </attribute>
                    <attribute name="parameters">
                        <attribute>
                            <attribute name="in">body</attribute>
                            <attribute name="schema">
                                <attribute name="type">object</attribute>
                                <attribute name="properties">
                                    <attribute name="name">
                                        <attribute name="type">string</attribute>
                                    </attribute>
                                    <attribute name="description">
                                        <attribute name="type">string</attribute>
                                    </attribute>
                                </attribute>
                            </attribute>
                            <attribute name="example">
                                <attribute name="name">Rabbit</attribute>
                                <attribute name="description">Pink rabbit</attribute>
                            </attribute>
                        </attribute>
                    </attribute>
                </attribute>
            </collectionOperation>
        </collectionOperations>
    </resource>
</resources>

Impact on swagger ui

# Changing the Swagger UI Location

Sometimes you may want to have the API at one location, and the Swagger UI at a different location. This can be done by disabling the Swagger UI from the API Platform configuration file and manually adding the Swagger UI controller.

# Disabling Swagger UI

# api/config/packages/api_platform.yaml
api_platform:
    # ...
    enable_swagger_ui: false

# Manually Registering the Swagger UI Controller

# app/config/routes.yaml
swagger_ui:
    path: /docs
    controller: api_platform.swagger.action.ui

Change /docs to your desired URI you wish Swagger to be accessible on.

# Using the Swagger Command

You can also dump your current Swagger documentation using the provided command:

$ docker-compose exec php bin/console api:swagger:export
# Swagger documentation in JSON format...

$ docker-compose exec php bin/console api:swagger:export --yaml 
# Swagger documentation in YAML format... 

$ docker-compose exec php bin/console api:swagger:export --output=swagger_docs.json 
# Swagger documentation dumped directly into JSON file (add --yaml to change format)

# Overriding the UI Template

As described in the Symfony documentation, it’s possible to override the Twig template that loads Swagger UI and renders the documentation:

{# templates/bundles/ApiPlatformBundle/SwaggerUi/index.html.twig #}
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>{% if title %}{{ title }} {% endif %}My custom template</title>
    {# ... #}
</html>

You may want to copy the one shipped with API Platform and customize it.

# Compatibilily Layer with Amazon API Gateway

AWS API Gateway supports OpenAPI partially, but it requires some changes. Fortunately, API Platform provides a way to be compatible with Amazon API Gateway.

To enable API Gateway compatibility on your OpenAPI docs, add api_gateway=true as query parameter: http://www.example.com/docs.json?api_gateway=true

You can also help us improve the documentation of this page.

Made with love by

Les-Tilleuls.coop can help you design and develop your APIs and web projects, and train your teams in API Platform, Symfony, Next.js, Kubernetes and a wide range of other technologies.

Learn more

Copyright © 2023 Kévin Dunglas

Sponsored by Les-Tilleuls.coop