Skill: Create an API Endpoint
Steps
1. Define the Route
In src/Module/{Module}/config/routes.yaml:
{module}_{action}:
path: /api/v1/{entities}
controller: App\Module\{Module}\Presentation\Controller\{Entity}Controller::{action}
methods: [GET|POST|PATCH|DELETE]
2. Create Request DTO (for POST/PATCH)
declare(strict_types=1);
namespace App\Module\{Module}\Presentation\DTO;
final readonly class {Action}{Entity}Request
{
public function __construct(
public string $field1,
public ?string $field2 = null,
) {}
}
3. Create Response DTO
declare(strict_types=1);
namespace App\Module\{Module}\Presentation\DTO;
final readonly class {Entity}Response
{
public function __construct(
public string $id,
public string $field1,
// ...
public string $createdAt,
) {}
public static function from{Entity}({Entity} $entity): self
{
return new self(
id: $entity->id()->value,
// ...
);
}
}
4. Create Controller Method
public function {action}(Request $request): JsonResponse
{
// 1. Parse/validate input
// 2. Create command/query
// 3. Dispatch to handler
// 4. Return response DTO
}
5. Write Integration Test
public function test_{action}_{entity}_returns_expected_status(): void
{
$client = static::createClient();
$client->request('POST', '/api/v1/{entities}', [], [], [
'CONTENT_TYPE' => 'application/json',
], json_encode([...]));
$this->assertResponseStatusCodeSame(201);
// Assert response body structure
}
6. Checklist
- Route registered
- Input validated (request DTO)
- Auth required (unless public endpoint)
- Tenant scoping applied
- Success response with correct status code
- Error responses for: validation, not found, unauthorized
- Integration test written