In this article, we will see how to create a REST API with FOS/RestBundle, including authentication, and a Swagger-like documentation generator.
List of necessary
bundles:
friendsofsymfony/rest-bundle:
Provides a set of tools to help develop a RESTful API
https://github.com/FriendsOfSymfony/FOSRestBundle
jms/serializer-bundle:
Allows object serialization.
https://packagist.org/packages/jms/serializer-bundle
lexik/jwt-authentication-bundle:
Manages JSON web tokens.
https://github.com/lexik/LexikJWTAuthenticationBundle
nelmio/NelmioApiDocBundle:
Generates Swagger-like HTML documentation
https://github.com/nelmio/NelmioApiDocBundle
Installation in composer
We will assume that we are using our skeleton as a starting point.
composer require "nelmio/cors-bundle"
composer require "lexik/jwt-authentication-bundle"
composer require "jms/serializer-bundle"
composer require "friendsofsymfony/rest-bundle"
composer require "nelmio/api-doc-bundle"
composer require "annotations"
Then we will need to register our bundles in bundles.php
Nelmio\CorsBundle\NelmioCorsBundle::class => ['all' => true],
Lexik\Bundle\JWTAuthenticationBundle\LexikJWTAuthenticationBundle::class => ['all' => true],
FOS\RestBundle\FOSRestBundle::class => ['all' => true],
Nelmio\ApiDocBundle\NelmioApiDocBundle::class => ['all' => true],
For documentation, the route will need to be added:
# config/routes.yaml
app.swagger_ui:
path: /api/doc
methods: GET
defaults: { _controller: nelmio_api_doc.controller.swagger_ui }
app.swagger:
path: /api/doc.json
methods: GET
defaults: { _controller: nelmio_api_doc.controller.swagger }
Create the configuration for the documentation:
# config/packages/nelmio_api_doc.yaml
nelmio_api_doc:
areas:
path_patterns: # an array of regexps
- ^/api(?!/doc$)
host_patterns:
- ^api\.
Then, we must activate the extra framework converters:
#/config/packages/sensio_framework_extra.yaml
sensio_framework_extra:
router:
annotations: false
request: { converters: true }
Creation of our first API method
First, we will create our controller
bin/console make:controller api
In our controller, we must include our FOSRestBundle's annotation library to use the right annotations, as well as NelmioApiDoc libraries
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use FOS\RestBundle\Controller\Annotations\Get;
use JMS\Serializer\SerializationContext;
use Symfony\Component\HttpFoundation\Response;
use Swagger\Annotations as SWG;
use Nelmio\ApiDocBundle\Annotation\Model;
use Nelmio\ApiDocBundle\Annotation\Security;
class ApiController extends AbstractController
{
/**
* @Get(
* path = "/api/test/{id}",
* name = "api_test_id",
* requirements = {"id"="\d+"}
* )
*/
public function index()
{
$detail=['test'=>'value'];
$serializer = $this->get('serializer');
$response = new Response(
$serializer->serialize(['detail' => $detail], 'json'),
Response::HTTP_OK
);
$response->headers->set('Content-Type', 'application/json');
return $response;
}
}
To manage response codes, we will have to use the following annotations:
* @SWG\Response(
* response=200,
* @SWG\Schema(type="object",
* example={"foo": "bar", "hello": "world"}
* ),
* description="Response ok"
* )
* @SWG\Response(
* response=401,
* description="Access Denied"
* )
* @SWG\Response(
* response=403,
* description="Forbidden"
* )
* @SWG\Response(
* response=404,
* description="Not Found"
* )

Authentication of our API
For authentication, we chose to keep it simple. Not a Bearer-type
header authentication, but just a key in the URL.
It's much
simpler to test, especially when you are working with third
parties, and you don't want to provide support everywhere and
train their interns. Even though adding a key in the header is
honestly not that complicated. Anyway.
So we will declare our key in the annotations of our method.
* @SWG\Parameter(
* name="key",
* in="query",
* type="string",
* description="The authorization key"
* )
Combined with our API definition, it looks like this:
/**
* @Get(
* path = "/api/test/{id}",
* name = "api_test_id",
* requirements = {"id"="\d+"}
* )
*
*
* @SWG\Parameter(
* name="key",
* in="query",
* type="string",
* description="The authorization key provided by HMF"
* )
And in Swagger, it will materialize like this:

Customization of the documentation
The documentation has some display issues, particularly with style,
and it displays a bundle logo when we want to display the client's
logo instead.
So, we simply need to override the template by
creating a Twig file
here:
/templates/bundles/NelmioApiDocBundle/SwaggerUi/index.html.twig
{# templates/bundles/NelmioApiDocBundle/SwaggerUi/index.html.twig #}
{#
To avoid a "reached nested level" error an exclamation mark `!` has to be added
See https://symfony.com/blog/new-in-symfony-3-4-improved-the-overriding-of-templates
#}
{% extends '@!NelmioApiDoc/SwaggerUi/index.html.twig' %}
{% block stylesheets %}
{{ parent() }}
<link rel="stylesheet" href="{{ asset('css/custom-swagger-styles.css') }}">
<style>
header #logo img{
height: unset;
}
header::before{
background-color: #FFFFFF;
}
.swagger-ui table tbody tr td, .response-col_description__inner{
padding: 0;
vertical-align: top;
}
.swagger-ui .markdown p{
margin: 10px auto;
}
</style>
{% endblock stylesheets %}
{% block javascripts %}
{{ parent() }}
<script type="text/javascript" src="{{ asset('js/custom-request-signer.js') }}"></script>
{% endblock javascripts %}
{% block header %}
<a id="logo" href="#"><img src="{{ asset('images/logo.png') }}" alt="Hyundai"></a>
{% endblock header %}
We also want to allow only JSON and testing in HTTPS, as our domain
is in HTTPS, and if the user (the intern) tests in HTTP while the
documentation is in HTTPS, the browser will block the request.
It's silly, but it's a real-world issue. 😊
We will also set a
title, remove the Bearer authentication, and eliminate host
verification.
# config/packages/nelmio_api_doc.yaml
nelmio_api_doc:
areas:
path_patterns: # an array of regexps
- ^/api(?!/doc$)
# host_patterns:
# - ^api\.
#
documentation:
host: api.hyundai.test
#schemes: [http, https]
schemes: [https]
info:
title: Hyundai API
description: hyundai backend webservice
version: 1.0.0
#securityDefinitions:
# Bearer:
# type: apiKey
# description: 'Value: Bearer {jwt}'
# name: Authorization
# in: header
#security:
# - Bearer: []
And there you have it! We are ready to develop our API.
What
remains is to create a CRUD for the keys and apply our logic for
the data to provide for each method.