illodev / api-platform-sdk-generator
Generate Zod schemas and typed HTTP repositories from your API Platform backend. Powered by Symfony Validator. Zero config.
Package info
github.com/illodev/api-platform-sdk-generator
Type:symfony-bundle
pkg:composer/illodev/api-platform-sdk-generator
Requires
- php: >=8.2
- api-platform/core: ^4.0
- doctrine/inflector: ^2.0
- symfony/config: ^6.4 || ^7.0
- symfony/console: ^6.4 || ^7.0
- symfony/dependency-injection: ^6.4 || ^7.0
- symfony/framework-bundle: ^6.4 || ^7.0
- symfony/http-kernel: ^6.4 || ^7.0
- symfony/validator: ^6.4 || ^7.0
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.64
- phpstan/phpstan: ^2.0
- phpstan/phpstan-symfony: ^2.0
- phpunit/phpunit: ^11.0
README
Generate Zod schemas and typed HTTP repositories from your API Platform backend. One source of truth (Symfony Validator) → typed clients in your frontend. Zero config.
Why
You already define your domain in API Platform: #[ApiResource] for endpoints, #[ApiProperty] for visibility, #[Assert\*] for validation. That metadata is the single source of truth for what your API accepts and returns.
This bundle reads that metadata and generates a TypeScript SDK against it:
- Zod schemas (request + response) for full-stack validation.
- Typed repositories with HTTP CRUD methods, ready to plug into TanStack Query or any data-fetching layer.
- Resource keys (constants) usable as query keys.
- Enums mirrored as TypeScript const objects.
No DTOs to keep in sync. No OpenAPI parser in the middle. No type loss at the boundary.
import { productRepository } from "@/sdk/repositories"; import type { ProductResponse } from "@/sdk/schemas"; const products = await productRepository.getCollection(); // ^? GetCollectionResponse<ProductResponse>
Companion runtime — coming in v0.2.0
Generated repositories import from a thin runtime that defines Repository<TOutput, TInput>. v0.1.0 ships the generator alone: you provide the runtime (interface documented below).
v0.2.0 will publish @illodev/repository to npm — a fully typed, TanStack Query–powered runtime with Repository, useGet, useGetCollection, useSuspenseGet, usePost, usePatch, useDelete, sub-resource hooks, and more.
If you want to ship today, follow the runtime interface below. If you want to wait a week, the npm package lands soon.
Maintenance commitment
This bundle is extracted from Fube, a Spanish invoicing SaaS in production. We use it daily — bugs surface and get fixed under real-world load, not in isolation. As close to "battle-tested" as it gets without writing it yourself.
Requirements
- PHP 8.2+
- Symfony 6.4+ (7.x supported)
- API Platform 4.0+
Install
composer require --dev illodev/api-platform-sdk-generator
With Symfony Flex, the bundle is auto-registered and config/packages/illodev_sdk.yaml is created with sensible defaults. Manual registration:
// config/bundles.php return [ // ... Illodev\ApiPlatformSdkGenerator\IllodevSdkBundle::class => ['dev' => true, 'test' => true], ];
Usage
bin/console illodev:sdk:generate
Schemas, repositories, keys and enums are written to the configured output path (%kernel.project_dir%/assets/sdk by default).
Configuration
# config/packages/illodev_sdk.yaml illodev_sdk: output: path: '%kernel.project_dir%/assets/sdk' naming: PascalCase # PascalCase | camelCase resources: include: ~ # null = all #[ApiResource] discovered exclude: [] # FQCN to exclude explicitly
What it generates
For each #[ApiResource]:
| Output | Path | Description |
|---|---|---|
| Zod schemas | schemas/<resource>.ts |
<Resource>RequestSchema, <Resource>ResponseSchema, plus z.infer types. |
| Repositories | repositories/<resource>.repository.ts |
<resource>Repository = createRepository<Response, Request>(<RESOURCE_KEY>). |
| Resource keys | schemas/keys.ts |
String constants usable as query keys. |
| Enums | schemas/enums/ |
TypeScript const objects mirroring PHP enums. |
Runtime interface
Until @illodev/repository lands in v0.2.0, generated repositories import createRepository and a Repository type from a path you control. Minimum interface:
export interface Repository<TOutput, TInput> { key: string; get(args: { identifier: string | number; uriTemplate?: string; request?: RequestInit; params?: SearchParams }): Promise<TOutput>; getCollection(args: { params?: SearchParams; request?: RequestInit }): Promise<GetCollectionResponse<TOutput>>; post(args: { data: TInput; request?: RequestInit }): Promise<TOutput>; postMany(args: { data: TInput[]; request?: RequestInit }): Promise<TOutput[]>; patch(args: { identifier: string | number; data: Partial<TInput>; request?: RequestInit }): Promise<TOutput>; patchMany(args: { identifiers: string[]; data: Partial<TInput>; request?: RequestInit }): Promise<TOutput[]>; delete(args: { identifier: string | number; request?: RequestInit }): Promise<void>; deleteMany(args: { identifiers: string[]; request?: RequestInit }): Promise<void>; // sub-resources: getSubresource, getSubresourceCollection, postSubresource } export interface GetCollectionResponse<T> { rows: T[]; count: number; } export type SearchParams = Record<string, string | number | boolean | string[] | undefined>; export function createRepository<TOutput, TInput>(key: string): Repository<TOutput, TInput>;
Reference implementation will ship as @illodev/repository in v0.2.0.
Known limitations (v0.1.0)
- Serialization Groups (
#[Groups]) are not supported. Property visibility is derived exclusively from#[ApiProperty(readable, writable)]. If your project relies heavily on groups, this bundle is not yet a fit. - Nested
#[Assert\Collection]and#[Assert\All]for JSON columns may produce partial type extraction at deeper nesting levels. See issues taggedassert-nestedfor reproducible cases and workarounds.
Roadmap
- v0.2.0 —
@illodev/repositorypublished to npm: full TanStack Query–powered runtime. - v0.3.0+ — driven by community feedback. Likely candidates: alternative schema libraries (Valibot, ArkType), Serialization Groups support if requested.
Contributing
PRs welcome. Please open an issue first describing the change. See CONTRIBUTING.md.
composer install composer test # PHPUnit composer analyse # PHPStan level 8 composer cs-check # PHP-CS-Fixer dry-run
Sponsors
If this bundle saves you time, consider sponsoring via GitHub Sponsors (activated after v0.1.0).
License
MIT © Álvaro Jáuregui Pinto. See LICENSE.
Built with care by illodev.