Forum guided project

API Endpoints

Step 12 / 13 Beginner to intermediate SQLite to MySQL path

API Endpoints

API controllers live under src/ApiControllers. The forum API exposes write endpoints that use the same permissions, write service, and public-demo protection as the web forms.

Goal

Create JSON endpoints for topic creation, reply creation, and moderation attempts.

File Created

src/ApiControllers/ForumDemoController.php

Step: Create The API Controller

<?php
declare(strict_types=1);

namespace ApiControllers;

use Modules\ForumDemo\Auth\DemoAuth;
use Modules\ForumDemo\Permissions\DemoPermissionService;
use Modules\ForumDemo\Writes\ForumWriteService;
use Modules\ForumDemo\Writes\PublicDemoWriteGuard;

final class ForumDemoController
{
    private DemoAuth $auth;
    private ForumWriteService $writeService;
    private PublicDemoWriteGuard $demoWriteGuard;
}

API controllers are separate from web controllers so JSON behavior does not leak into templates.

Step: Build Dependencies

public function __construct()
{
    $permissions = new DemoPermissionService();
    $this->auth = new DemoAuth();
    $this->writeService = new ForumWriteService($permissions);
    $this->demoWriteGuard = new PublicDemoWriteGuard($permissions);
}

This mirrors the web controller permissions and write behavior.

Step: Add Convention-Based Methods

CorianderPHP maps API paths to methods by convention.

public function post_topic(): array
{
    return $this->write('topic.create', 'create topic');
}

public function post_reply(): array
{
    return $this->write('reply.create', 'create reply');
}

public function post_moderate(): array
{
    return $this->write('reply.moderate', 'moderate reply');
}

Returning an array lets the framework encode JSON for you.

Step: Read JSON Payloads

private function write(string $ability, string $action): array
{
    $payload = json_decode((string) file_get_contents('php://input'), true);
    $payload = is_array($payload) ? $payload : $_POST;

    $user = $this->auth->currentUser();
    $demoResult = $this->demoWriteGuard->protect($user, $ability, $action, $payload);

    return $demoResult ?? $this->writeService->run($user, $ability, $payload);
}

The API and web controllers both check the same public-demo protection before calling the real write service.

For reply.create, include topic_id in the JSON body so ForumWriteService::run() can call createReply() with the correct topic.

API Routes

POST /api/forum-demo/topic
POST /api/forum-demo/reply
POST /api/forum-demo/moderate

CSRF Difference

The framework CSRF middleware protects mutating web requests, but skips /api/*. That keeps APIs suitable for stateless clients.

If an API endpoint is only used by same-site pages, you can validate tokens manually, but make that decision explicit.

Checkpoint

Call /api/forum-demo/topic with a JSON body as a guest. The response should be JSON and should reject the action because guests cannot create topics.

Common Mistakes

  • Returning HTML from API controllers.
  • Implementing different permission rules for API and web requests.
  • Assuming CSRF behavior is the same for /api/* and web routes.

Next

Continue with MySQL And Production.

Live version

Open the safe demo

Use the demo when a guide step asks you to verify behavior. Local projects write to SQLite; this public demo validates writes without persisting visitor content.

Open forum demo

Project files

Download project files

This download does not include the CorianderPHP framework. Start from a CorianderPHP project, then use the completed app files as a reference.

Download completed app