wernerdweight/doctrine-crud-api-bundle
A Symfony bundle for quickly exposing Doctrine entities as CRUD REST API endpoints, with configurable operations, filtering, pagination, and serialization to reduce boilerplate in API development.
composer require wernerdweight/doctrine-crud-api-bundle), then confirm it is registered in config/bundles.php (Symfony Flex may not auto-register third-party bundles at this size — add Wernerdweight\DoctrineCrudApiBundle\DoctrineCrudApiBundle::class => ['all' => true] manually if missing).# config/routes/wernerdweight_crud_api.yaml
wernerdweight_doctrine_crud_api:
resource: '@DoctrineCrudApiBundle/Resources/config/routing.yaml'
prefix: /api
php bin/console config:dump-reference doctrine_crud_api) — this is the single most important file to read before annotating anything, because it defines which entities are exposed, which operations (list/get/create/update/delete) are enabled, and the serialization groups used.src/ directory is the documentation. Start with the entity-level attribute/annotation class (e.g. something like #[CrudApi] or #[ApiResource] — check the actual namespace under Attribute/ or Annotation/) and the controller that resolves it. That one file tells you the entire contract.GET /api/{slug} — you should get a paginated JSON collection. That single round-trip validates: routing import is correct, the entity is registered in config, the serializer is wired, and Doctrine metadata is being read.The mental model: your Doctrine mapping is the API schema. Columns become fields, relations become nested/included resources, and the bundle turns metadata into routes + controllers + serializers. If it isn't in the mapping, it isn't in the API.
Attribute-driven resource definition. The idiomatic pattern is to keep API concerns colocated with the entity: a class-level attribute (with the route slug and operations), plus field-level attributes/groups controlling visibility and writability. This is where you spend most of your configuration effort.
Per-operation overrides. Expect the bundle to let you disable operations per entity (read-only entities by omitting create/update/delete). Use this aggressively: exposing all five verbs on every entity is the fastest way to ship an accidental admin API.
Serialization groups. Most Doctrine-CRUD-API bundles lean on Symfony's serializer with groups. The pattern that scales: define a small set of named groups (list, detail, write) and tag properties with them. The list group should carry only the fields a table view needs (id, label, timestamps), the detail group the full record. Without this split you either leak columns or N+1 the relation graph on index endpoints.
Pagination + filtering via query params. Configure a default page size and max page size in the bundle config; rely on the bundle's built-in page, limit, sort, and filter[...]-style parameters rather than adding custom controllers. For anything the built-in filtering can't express (joins across relations, computed columns), reach for a custom controller — but keep the same response envelope so clients don't need to special-case.
Validation. If the bundle respects Symfony Validator constraints on your entity, annotate the entity with constraints and let the framework reject bad payloads with a 400 before Doctrine ever sees them. This is much cleaner than validating in a controller.
Integration with existing Doctrine mapping. For projects already using Doctrine relations, the key win is fetch=EAGER vs the bundle's serializer strategy. Test index endpoints with real data volumes — relation traversal is where these bundles typically blow up.
Overriding controllers. Most small bundles let you swap the generated controller class in config or via a service decorator. If you need custom security/authorization logic, decorate the bundle's controller service rather than copying it — copies drift on upgrade.
"wernerdweight/doctrine-crud-api-bundle": "1.x" with composer lock committed), vendor the source if you need to patch, and budget time to read src/ before assuming a feature exists. There is no ecosystem to fall back on./api/{entity} routes or an API Platform install, importing this bundle's routing under the same prefix will silently shadow handlers. Use a distinct prefix (/admin/api, /internal/api) or verify with php bin/console debug:router.php bin/console cache:clear and, in prod, doctrine:cache:clear-metadata. This is the single most common "my change did nothing" cause.Author.photos ↔ Photo.author) will cause circular reference errors unless you either exclude the inverse side from the read group or configure the serializer's circular-reference handler. Always test the detail endpoint, not just index.#[ORM\Index] on filter columns, and consider overriding the list action with a custom repository query (->leftJoin()->addSelect()) if the default one lazy-loads.#[IsGranted] setup unless the routes it emits pass through your firewall and controller-level security. Verify with the Symfony profiler that security.access_control in security.yaml actually matches the generated paths — a wrong prefix here is the most damaging mistake, because a "working" API may be fully public.ClassMetadata for the entity — the bundle is almost always faithfully serializing what Doctrine reports, and the field you expected may be a to-one association that needs a nested group.php bin/console debug:container --parameter or grep for the entity registration service if a route is missing.doctrine_crud_api-scoped event dispatcher or subscriber for pre/post persistence, and the controller service ID for decoration. If none of these are documented, assume the bundle exposes services under an doctrine_crud_api.* prefix and inspect php bin/console debug:container | grep doctrine_crud.How can I help you explore Laravel packages today?