Skill: Create an API Endpoint

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