How to Have Good API Documentation in Symfony
API documentation is one of those things every developer understands is important, but it’s also quite easy to put off until “later”, which (let’s be honest) might never actually happen. Whether you’re building a project solo or as part of a team, good documentation is a lifesaver for both your current self and anyone who might (or will!) work with your API in the future.
Why Good API Documentation Matters
Let’s set the stage: you’re building an API backend with Symfony, and there’s a frontend team eagerly waiting to hook into your endpoints. Without clear, accessible documentation, every conversation becomes an ad-hoc Q&A session. Details get lost, mistakes creep in, and for every new teammate, the onboarding cost gets higher and higher. Good API docs are like a friendly guide—empowering your frontend team to work independently and confidently.
But Doesn't Symfony "Just Solve" This?
If you’re using Symfony, you might have noticed it gently nudges you towards API Platform. And honestly, API Platform is a fantastic tool. It comes with a ton of power: automatic Swagger docs, serialization, validation, scaffolding—you name it. But here’s the catch: it brings along a lot of dependencies, including Doctrine. Maybe your project is simple and doesn’t need these extra complexities, or you want to stay lightweight for performance or maintenance reasons.
That’s where NelmioApiDocBundle comes in—and why it’s been my favorite way to document APIs in Symfony projects that don’t need the full power (and overhead) of API Platform.
Enter NelmioApiDocBundle: Lightweight OpenAPI Documentation
NelmioApiDocBundle is a tried-and-tested Symfony bundle that generates beautiful OpenAPI (Swagger) documentation for your REST APIs. It requires only minimal setup, doesn’t force Doctrine or heavy dependencies on your project, and still gives you all the Swagger UI goodness developers love.
Let’s Walk Through a Simple Example
Suppose we have a project with a basic Book model and a controller that returns a list of books. Here’s what it looks like without any documentation:
<?php
// src/Model/Book.php
namespace App\Model;
class Book
{
private string $title;
private string $author;
private string $isbn;
public function __construct(string $title, string $author, string $isbn)
{
$this->title = $title;
$this->author = $author;
$this->isbn = $isbn;
}
// ... getters ...
}
<?php
// src/Controller/BookController.php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use App\Repository\BookRepository;
class BookController extends AbstractController
{
#[Route('/api/books', name: 'api_books')]
public function index(BookRepository $bookRepository): Response
{
$books = $bookRepository->findAll();
return $this->json($books);
}
}
This works, but… anyone who wants to use the /api/books endpoint has to either read your code or ping you in chat. Not ideal!
Step 1: Install NelmioApiDocBundle
Let’s get NelmioApiDocBundle installed:
composer require nelmio/api-doc-bundle twig asset
This adds the bundle and ensures Twig and Asset components are present to serve the Swagger UI.
Step 2: Enable the API Documentation Routes
Add the following in ./config/routes/nelmio_api_doc.yaml:
app.swagger:
path: /api/doc.json
methods: GET
defaults: { _controller: nelmio_api_doc.controller.swagger }
app.swagger_ui:
path: /api/doc
methods: GET
defaults: { _controller: nelmio_api_doc.controller.swagger_ui }
What does this do? You now have two new routes: /api/doc (the graphical Swagger UI) and /api/doc.json (the raw OpenAPI spec).
Step 3: Annotate Your Models and Controllers
This is where the magic happens! By using PHP 8+ attributes (annotations), we attach OpenAPI documentation directly to our models and controller actions. First, update Book.php:
<?php
namespace App\Model;
use OpenApi\Attributes as OA;
class Book
{
#[OA\Property(description: 'The title of the book', type: 'string', example: 'The Hitchhiker\'s Guide to the Galaxy')]
public string $title;
#[OA\Property(description: 'The author of the book', type: 'string', example: 'Douglas Adams')]
public string $author;
#[OA\Property(description: 'The International Standard Book Number', type: 'string', example: '978-0345391803')]
public string $isbn;
// ... constructor and getters ...
}
Notice how each property is now described for API consumers: what it is, its type, and an example value.
Next, let’s update the controller method to document the endpoint:
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use App\Repository\BookRepository;
use App\Model\Book;
use OpenApi\Attributes as OA;
use Nelmio\ApiDocBundle\Attribute\Model;
class BookController extends AbstractController
{
/**
* List all books.
*/
#[Route('/api/books', name: 'api_books', methods: ['GET'])]
#[OA\Response(
response: 200,
description: 'Returns the list of books',
content: new OA\JsonContent(
type: 'array',
items: new OA\Items(ref: new Model(type: Book::class))
)
)]
#[OA\Tag(name: 'Books')]
public function index(BookRepository $bookRepository): Response
{
$books = $bookRepository->findAll();
return $this->json($books);
}
}
This setup tells Nelmio/OpenAPI: “the response here is an array of Book objects, look at this model for field definitions.” You can group endpoints into logical tags (like “Books”) so your API docs stay organized.
What You'll See (and Why It’s Awesome)
- Swagger UI at /api/doc: A clickable, browsable API interface showing all endpoints, expected request and response bodies, error codes, and data models.
- Frontend Teams Love You: Complete, up-to-date docs generated directly from your code—with zero extra manual work. Less back-and-forth, more self-service, fewer headaches!
- No Extra Weight: No need to drag in Doctrine (if you don’t want it), no overengineering. Just what you need, nothing more.
Tips & Good Practices
- Keep Annotations Up to Date: As your API evolves, so should your docs. Teach your team to update annotations as part of new features (or PR checklists).
- Describe Edge Cases: Don’t hesitate to annotate status codes, validation errors, or security constraints.
- Use Example Payloads: Concrete examples help consumers understand what to send and expect.
- Add Meta Info in nelmio_api_doc.yaml: You can configure API title, description, contact info, and more, making your docs look even more polished.
Wrapping Up
Robust, discoverable API documentation shouldn’t be an afterthought—and with NelmioApiDocBundle in Symfony, it doesn’t have to be. It’s simple, fast, and will keep both your backend and frontend teams happy and productive.
Next time you spin up a new API in Symfony, give NelmioApiDocBundle a try. Your future self (and your teammates) will thank you!
Happy documenting! 🚀