set up cleanly, from zero to Swagger UI
Anyone running a Symfony REST API without documentation leaves the tedious reverse engineering work to consumers and QA teams. NelmioApiDocBundle generates a complete OpenAPI 3.1 specification from PHP attributes and YAML configuration, including Swagger UI, JWT security schemes, and response schemas straight from the code.
Table of Contents
- 1. Why NelmioApiDocBundle instead of manual YAML specs
- 2. Installation and bundle configuration
- 3. OpenAPI attributes in Symfony controllers
- 4. Request and response schemas with PHP classes
- 5. Security schemes: configuring JWT Bearer and API key
- 6. API groups and versioning
- 7. Deploying Swagger UI safely in production
- 8. Exporting and testing the OpenAPI spec as JSON/YAML
- 9. Configuration approaches compared
- 10. Summary
- 11. FAQ
1. Why NelmioApiDocBundle instead of manual YAML specs
REST API documentation maintained by hand as a YAML file goes stale with the first commit that skips a doc update. In practice, this creates discrepancies between the spec and actual behavior: an endpoint suddenly expects an extra required field, the Swagger file knows nothing about it, and the first consumer finds out via a 400 error. NelmioApiDocBundle solves this problem by deriving the OpenAPI specification directly from the PHP code, from routing metadata, PHP attributes on controllers, and typed request/response classes.
The second advantage lies in IDE support. PHP attributes are type-safe, and PhpStorm shows autocompletion and validation errors right while you type. Anyone who has edited YAML in the past and introduced typos that only surfaced at deployment time appreciates the static checkability immediately. The bundle also integrates seamlessly with Symfony Security, recognizes firewall-protected routes, and can automatically carry their security requirements over into the spec.
Third, the Swagger UI that ships with the bundle is directly reachable from the Symfony development server and always shows the current state of the spec, without a separate build step, without file synchronization. For teams working on backend and frontend at the same time, that is a clear efficiency gain over static spec files sitting in the repository.
2. Installation and bundle configuration
Installation happens via Composer. The bundle requires at least PHP 8.1 and Symfony 6.0. From PHP 8.2 onward, using PHP attributes instead of annotations is recommended, since annotations (Doctrine-style with @OA\...) are no longer preferred in Symfony 7. After installation, Symfony Flex automatically registers the bundle in config/bundles.php and creates a base configuration under config/packages/nelmio_api_doc.yaml.
The bundle's configuration splits into two areas: the global API metadata (title, version, description, server URLs) and the routing filters that determine which endpoints get included in the spec. The latter matters for excluding internal admin routes or health-check endpoints from the public documentation. The bundle supports multiple named sections (areas), so you can generate separate specs for a public API and an internal admin API from the same codebase.
# Install NelmioApiDocBundle via Composer
composer require nelmio/api-doc-bundle
# Install Swagger-UI assets (needed for the web UI)
composer require symfony/asset
# Optionally: install form type support for request body inference
composer require symfony/form
# Generate config skeleton if not auto-generated
php bin/console config:dump nelmio_api_doc
# config/packages/nelmio_api_doc.yaml
nelmio_api_doc:
documentation:
info:
title: "Mironsoft REST API"
description: "Public API for Mironsoft platform services"
version: "1.0.0"
servers:
- url: "https://api.mironsoft.de/v1"
description: "Production"
- url: "http://localhost:8000/v1"
description: "Development"
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
areas:
public:
path_patterns:
- "^/api/v1"
host_patterns: []
admin:
path_patterns:
- "^/api/admin"
3. OpenAPI attributes in Symfony controllers
Since PHP 8.0, native attributes are the preferred way to attach OpenAPI metadata directly to controller methods. The OpenApi\Attributes namespace of the zircote/swagger-php package, which NelmioApiDocBundle uses internally, provides all the attributes you need: #[OA\Get], #[OA\Post], #[OA\Parameter], #[OA\RequestBody], and #[OA\Response]. These attributes sit directly on the controller action and precisely describe inputs, outputs, and possible error responses.
Choosing the right level of abstraction matters. Individual parameters like path IDs or query filters belong as #[OA\Parameter] on the action. Request bodies with complex JSON structures, on the other hand, should reference their own schema classes decorated with #[OA\Schema]. Anyone who describes every field name inline in the controller attribute ends up with unreadable attribute blocks and loses the reusability of schemas across multiple endpoints.
<?php
// src/Controller/Api/V1/ProductController.php
declare(strict_types=1);
namespace App\Controller\Api\V1;
use OpenApi\Attributes as OA;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Routing\Attribute\Route;
#[OA\Tag(name: 'Products')]
#[Route('/api/v1/products')]
class ProductController extends AbstractController
{
/**
* Retrieve a single product by its ID.
*/
#[OA\Get(
path: '/api/v1/products/{id}',
summary: 'Get product by ID',
parameters: [
new OA\Parameter(name: 'id', in: 'path', required: true,
schema: new OA\Schema(type: 'integer', minimum: 1))
],
responses: [
new OA\Response(response: 200, description: 'Product found',
content: new OA\JsonContent(ref: '#/components/schemas/ProductResponse')),
new OA\Response(response: 404, description: 'Product not found',
content: new OA\JsonContent(ref: '#/components/schemas/ErrorResponse')),
]
)]
#[Route('/{id}', methods: ['GET'])]
public function show(int $id): JsonResponse
{
// ... controller logic
}
}
4. Request and response schemas with PHP classes
Reusable OpenAPI schemas are best defined as dedicated PHP classes or DTOs annotated with #[OA\Schema]. Each property of the class gets an #[OA\Property] attribute with type, description, and optional validation information such as minimum, maxLength, or enum. These classes sensibly live in the App\Api\Schema\ or App\Dto\ namespace, separate from entities and services.
A decisive advantage of this approach: the same DTO classes used for the OpenAPI spec can also serve as Symfony form types or as targets for the Symfony Serializer. Combined with the Symfony Validator, validation rules and API schema are defined in a single place, and both, runtime validation and generated documentation, stay automatically in sync. That is the core idea of the code-first approach versus the design-first alternative.
5. Security schemes: configuring JWT Bearer and API key
NelmioApiDocBundle supports all security scheme types defined in OpenAPI 3.x: HTTP Bearer (for JWT), API key (header or query parameter), and OAuth2 with various flows. Configuration happens either globally in the bundle's YAML configuration (for scheme definitions) or per endpoint as an #[OA\Security] attribute (to specify which scheme the given endpoint requires). Routes protected by Symfony's firewall with stateless: true and JWT must be explicitly annotated with #[OA\Security(name: 'bearerAuth')], since the bundle does not automatically evaluate the firewall configuration.
In Swagger UI, the Authorize button then appears in the top right, letting testers enter a JWT token. The token gets sent as an Authorization header for all subsequent try-it-out requests. For internal APIs it makes sense to define a second security section that uses a long-lived API key scheme, for example for machine-to-machine communication where a short-lived JWT token is not practical.
# config/packages/security.yaml (relevant JWT firewall snippet)
security:
firewalls:
api:
pattern: ^/api/v1
stateless: true
jwt: ~
# In controller action, apply security requirement to endpoint:
# #[OA\Security(name: 'bearerAuth')]
# config/packages/nelmio_api_doc.yaml, global security default
nelmio_api_doc:
documentation:
security:
- bearerAuth: []
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
apiKey:
type: apiKey
in: header
name: X-API-KEY
6. API groups and versioning
Larger APIs often consist of multiple versions or logical groups, a public v1 API for external consumers, a v2 API with breaking changes, and an admin API for internal management operations. NelmioApiDocBundle models this separation through named areas. Each area has its own path_patterns, its own security defaults, and is served under its own URL: /api/doc/public.json versus /api/doc/admin.json.
Versioning in the URL path (/api/v1/ vs. /api/v2/) is the most common strategy and easy to implement with the bundle. Anyone who prefers header-based versioning (Accept: application/vnd.mironsoft.v2+json) can implement it with custom request matchers, but must adjust the routing patterns accordingly. With URL versioning, it makes sense to organize routes in separate files and include them via config/routes/api_v1.yaml and config/routes/api_v2.yaml, so the bundle's area filtering works correctly.
7. Deploying Swagger UI safely in production
NelmioApiDocBundle's Swagger UI is reachable at /api/doc in development mode and usable immediately. In production, two things need attention: first, the route to Swagger UI should sit behind an authentication wall, nobody should be able to view the full API documentation, including security schemes, without logging in. Second, the dynamic spec generation causes CPU load on every request, since the bundle scans all controllers for attributes. In production, the spec should be cached or served as a static file.
The simplest safeguard is a separate Symfony firewall entry for /api/doc that uses HTTP Basic or an IP whitelist as authentication. For teams with a CI/CD pipeline, it makes sense to generate the OpenAPI spec as a build artifact (bin/console nelmio:apidoc:dump --area=public > public/api-spec.json) and have Swagger UI served statically from an Nginx instance that references the JSON file. This eliminates the runtime overhead entirely.
8. Exporting and testing the OpenAPI spec as JSON/YAML
NelmioApiDocBundle ships with a console command that exports the generated spec into various formats. bin/console nelmio:apidoc:dump --area=public --format=json outputs the complete OpenAPI 3.x specification as JSON on stdout. Piping into jq . lets you format the output, or use python3 -m json.tool to check syntactic correctness. The YAML export (--format=yaml) is easy to read and serves as a good basis for manual additions or as input for other tools.
For automated validation in the CI pipeline, swagger-cli validate api-spec.json or the spectral lint command from the Stoplight ecosystem is recommended. Spectral supports custom rule sets that let you enforce coding guidelines for the API spec: required fields in response schemas, forbidden field names, consistent naming conventions. This validation runs in seconds and prevents incomplete or inconsistent specs from landing on the main branch.
9. Configuration approaches compared
There are several ways to maintain OpenAPI documentation in a Symfony project. The choice of approach significantly affects maintainability, completeness, and integration effort.
| Approach | Advantage | Disadvantage | Recommendation |
|---|---|---|---|
| Manual YAML spec | Full control, no PHP overhead | Goes stale quickly, high sync effort | Only for design-first with code generation |
| PHP Attributes (NelmioApiDocBundle) | Always current, IDE support, reusability | Attributes on the controller get verbose | Recommended for existing Symfony apps |
| API Platform | Fully automatic from entities | Heavily opinionated, hard to customize | Good for CRUD-heavy resource APIs |
| Spec generator from tests | Spec directly from real behavior | Complex, requires a dedicated test framework | As a complement to NelmioApiDocBundle |
| Swagger-PHP alone | No bundle, lightweight | No Symfony integration, manual routes | Only if no Symfony bundle is wanted |
For most Symfony projects, combining PHP attributes with NelmioApiDocBundle plus CI-side Spectral validation is the most pragmatic path. The attributes stay close to the code, the spec is always current, and the validation step ensures quality standards are met, without a developer having to maintain a YAML file by hand.
Mironsoft
REST API design, Symfony development, and OpenAPI documentation
Need a Symfony REST API with complete OpenAPI documentation?
We set up NelmioApiDocBundle in existing Symfony projects, migrate outdated annotations to PHP attributes, and integrate spec validation into your CI/CD pipeline.
Bundle setup
Setting up installation, configuration, routing filters, and security schemes
Schema design
Request and response DTOs with complete OpenAPI schemas and validation
CI integration
Spectral linting, spec export as an artifact, and securing Swagger UI for production
10. Summary
NelmioApiDocBundle is the most pragmatic way to integrate OpenAPI 3.1 into Symfony projects without having to maintain a separate spec file. PHP attributes on controllers and schema classes keep documentation and code in sync. Named areas cleanly separate public and internal APIs. Security schemes for JWT Bearer and API key are defined in the bundle's YAML configuration and applied per endpoint via #[OA\Security] attributes. The console export command makes the spec available as a static artifact for CI validation and production delivery.
The most important step after setup is integrating spectral lint into the CI pipeline. Without automated validation, missing response schemas, undocumented error responses, and inconsistent field names creep in, and these are exactly the details that cost API consumers time during integration. A valid OpenAPI document is the foundation for mock servers, client code generation, and security audits, all of which build on the same spec.
NelmioApiDocBundle, the essentials at a glance
Installation
composer require nelmio/api-doc-bundle, Symfony Flex sets up configuration and routing automatically. PHP 8.1+ and Symfony 6.x required.
PHP attributes
#[OA\Get], #[OA\Post], #[OA\Schema] directly on the controller and DTO, no YAML sync needed, IDE validation included.
Security
Define Bearer JWT and API key in nelmio_api_doc.yaml, apply per route with #[OA\Security(name: 'bearerAuth')].
Production
Export the spec with bin/console nelmio:apidoc:dump, serve Swagger UI behind a firewall, and integrate Spectral validation into CI.