## Getting Started
### Minimal Setup
1. **Install the package**:
```bash
composer require cesurapp/api-bundle
config/packages/api.yaml:
api:
cors_header:
- { name: 'Access-Control-Allow-Origin', value: '*' }
thor:
base_url: "%env(APP_DEFAULT_URI)%"
ApiController and use ApiResponse:
use Cesurapp\ApiBundle\AbstractClass\ApiController;
use Cesurapp\ApiBundle\Response\ApiResponse;
class TestController extends ApiController {
public function index(): ApiResponse {
return ApiResponse::create()->setData(['test' => 'data']);
}
}
/thor and generate TypeScript clients:
bin/console thor:extract ./client
Create a UserController with DTO validation and resource transformation:
use Cesurapp\ApiBundle\AbstractClass\ApiController;
use Cesurapp\ApiBundle\Response\ApiResponse;
use Cesurapp\ApiBundle\Thor\Attribute\Thor;
class UserController extends ApiController {
#[Thor(
title: 'Create User',
request: ['name' => 'string', 'email' => 'string'],
response: [200 => ['data' => UserResource::class]],
dto: CreateUserDto::class
)]
public function create(CreateUserDto $dto): ApiResponse {
return ApiResponse::create()->setData($dto->toArray());
}
}
ApiController for automatic JSON request parsing and error handling.[Thor] to auto-generate documentation and TypeScript clients.
#[Thor(
stack: 'User|1',
query: ['filter[name]' => '?string'],
isPaginate: true
)]
ApiResponse methods:
return ApiResponse::create()
->setData($user)
->setResource(UserResource::class)
->setPaginate()
->setHTTPCache(3600);
PhoneNumber, UniqueEntity):
class LoginDto extends ApiDto {
#[Assert\NotNull]
#[PhoneNumber]
public string $phone;
#[Assert\NotNull]
#[UniqueEntity(entityClass: User::class, field: 'email')]
public string $email;
}
beforeValidated()/endValidated() for custom logic:
protected function beforeValidated(): void {
$this->email = strtolower($this->email);
}
ApiResourceInterface to define API output:
class UserResource implements ApiResourceInterface {
public function toArray(User $user): array {
return ['id' => $user->id, 'name' => $user->name];
}
public function toResource(): array {
return [
'name' => [
'type' => 'string',
'filter' => fn(QueryBuilder $qb, string $alias, $data) =>
$qb->andWhere("$alias.name LIKE :name")->setParameter('name', "%$data%"),
],
];
}
}
setQuery() for filtering/sorting:
return ApiResponse::create()
->setQuery($repo->createQueryBuilder('u'))
->setResource(UserResource::class);
toResource() to enable dynamic query filtering:
GET /users?filter[name]=John&filter[createdAt][from]=2024-01-01
use Cesurapp\ApiBundle\Exporter\ExcelExporter;
$exporter = new ExcelExporter();
return $exporter->export($users, 'users.xlsx');
->setHTTPCache(60, tags: ['users'])
bin/console thor:extract ./client
import { ApiClient } from './client';
const client = new ApiClient();
const users = await client.get('/users');
Thor Configuration Conflicts:
thor.base_url breaks TypeScript client generation.APP_DEFAULT_URI in .env matches your API base URL.
thor:
base_url: "%env(APP_DEFAULT_URI)%" # e.g., "https://api.example.com"
DTO Validation Short-Circuiting:
beforeValidated() may bypass Symfony constraints.parent::beforeValidated() if extending ApiDto:
protected function beforeValidated(): void {
parent::beforeValidated(); // Ensure constraints run
$this->email = strtolower($this->email);
}
Pagination Edge Cases:
setPaginate() without a QueryBuilder throws errors.->setQuery($repo->createQueryBuilder('u'))
->setPaginate()
Resource Filtering:
toResource() filters only work with pagination enabled (isPaginate: true).isPaginate: true in [Thor] if using filters.CORS Headers:
cors_header may conflict with Symfony’s built-in CORS.# config/packages/nelmio_cors.yaml
nelmio_cors:
enabled: false
Validation Errors:
errors in HTTP 422 responses for constraint violations.$dto->validate(throw: true); // Throws exceptions in dev
Thor Docs:
[Thor] annotations:
bin/console cache:clear
QueryBuilder Issues:
dd($query->getSQL()) to debug generated SQL for filters/sorting.Custom Validators:
AbstractValidator for reusable rules:
use Cesurapp\ApiBundle\Validator\AbstractValidator;
class CustomValidator extends AbstractValidator {
public function validate($value, Constraint $constraint) {
return $value === 'expected';
}
}
Response Transformers:
ApiResponse to add custom headers or formats:
class CustomApiResponse extends ApiResponse {
public function setCustomHeader(string $name, string $value): self {
$this->headers[$name] = $value;
return $this;
}
}
Thor Extensions:
Thor attribute:
#[Attribute(Attribute::TARGET_METHOD)]
class CustomThor extends Thor {
public string $customField;
}
$dto->auto = false for manual validation (faster for bulk operations).toResource() is called per-request; cache results if static:
private static ?array $resourceSchema;
public function toResource(): array {
return self::$resourceSchema ??= [
// cached schema
];
}
exception_converter: false in api.yaml to bypass automatic error formatting.thor.global_config:
thor:
global_config:
authHeader:
Authorization: 'Bearer {token}'
cors_header:
- { name: 'Access-Control-Allow-Origin', value: "%env(CORS_ORIGIN)%" }
How can I help you explore Laravel packages today?