Building JSON APIs

Learn how to create RESTful JSON APIs with TikNix. For framework basics, see the Getting Started guide.

API Controller Pattern

API Controller Example

<?php
namespace app;

class Api extends BaseControls\Control {

    public function __construct() {
        parent::__construct();

        // Set JSON headers
        header('Content-Type: application/json');

        // Check API authentication
        if (!$this->checkApiAuth()) {
            $this->json(['error' => 'Unauthorized'], 401);
            exit;
        }
    }

    // GET /api/users
    public function users($params = []) {
        $operation = $params['operation']->name ?? 'list';

        switch ($operation) {
            case 'list':
                $this->listUsers();
                break;
            case 'search':
                $this->searchUsers();
                break;
            default:
                $this->json(['error' => 'Invalid operation'], 400);
        }
    }

    private function listUsers() {
        // Pagination
        $page = $this->getParam('page', 1);
        $limit = $this->getParam('limit', 20);
        $offset = ($page - 1) * $limit;

        // Get users (automatically cached!)
        $users = R::find('member',
            'status = ? ORDER BY created_at DESC LIMIT ? OFFSET ?',
            ['active', $limit, $offset]
        );

        $total = R::count('member', 'status = ?', ['active']);

        $this->json([
            'success' => true,
            'data' => array_values(R::exportAll($users)),
            'meta' => [
                'total' => $total,
                'page' => $page,
                'limit' => $limit,
                'pages' => ceil($total / $limit)
            ]
        ]);
    }

    private function checkApiAuth() {
        $apiKey = $_SERVER['HTTP_X_API_KEY'] ?? '';

        if (empty($apiKey)) {
            return false;
        }

        // Check API key (cached!)
        $member = R::findOne('member', 'api_key = ? AND status = ?',
            [$apiKey, 'active']
        );

        if ($member) {
            $this->member = $member;
            return true;
        }

        return false;
    }
}

Testing API Endpoints

# GET request with API key
curl -H "X-API-Key: your-key-here" https://site.com/api/users

# POST with JSON data
curl -X POST https://site.com/api/users/create \
  -H "Content-Type: application/json" \
  -H "X-API-Key: your-key-here" \
  -d '{"username":"john","email":"john@example.com"}'

# With pagination
curl "https://site.com/api/users?page=2&limit=10" \
  -H "X-API-Key: your-key-here"

Response Helpers

JSON Responses

// Success response
$this->json([
    'success' => true,
    'message' => 'User created',
    'data' => ['id' => $id]
], 201);

// Error response
$this->json([
    'success' => false,
    'error' => 'Validation failed',
    'errors' => [
        'email' => 'Email already exists'
    ]
], 422);

// Using Flight directly
Flight::json(['data' => $data], 200);

Redirects

// Simple redirect
Flight::redirect('/dashboard');

// With query parameters
Flight::redirect('/auth/login?redirect=' . urlencode(Flight::request()->url));

// External redirect
Flight::redirect('https://example.com', 302);

Request Handling

Accessing Request Data

// Get Flight request object
$request = Flight::request();

// URL and path info
$url = $request->url;           // Full URL path
$method = $request->method;     // GET, POST, etc.
$ajax = $request->ajax;         // Is AJAX request?
$secure = $request->secure;     // Is HTTPS?
$ip = $request->ip;             // Client IP

// Query parameters (GET)
$page = $request->query->page ?? 1;
$search = $request->query['search'] ?? '';

// Body parameters (POST)
$username = $request->data->username;
$email = $request->data['email'];

// Headers
$apiKey = $request->header('X-API-Key');
$contentType = $request->header('Content-Type');

// Files
$uploadedFile = $request->files['upload'] ?? null;

Session Management

// Set session data
$_SESSION['user_preference'] = 'dark_mode';

// Get session data
$pref = $_SESSION['user_preference'] ?? 'light_mode';

// Login a user
$_SESSION['member'] = $member->export();

// Logout
unset($_SESSION['member']);
session_destroy();

Error Handling

// In controller constructor
set_exception_handler(function($e) {
    $this->logger->error($e->getMessage(), [
        'file' => $e->getFile(),
        'line' => $e->getLine()
    ]);

    if ($this->getParam('format') === 'json') {
        $this->json(['error' => 'Internal server error'], 500);
    } else {
        Flight::renderView('error/500');
    }
});

// Manual error handling
try {
    $result = $this->riskyOperation();
} catch (\Exception $e) {
    $this->logger->error('Operation failed: ' . $e->getMessage());
    $this->flash('error', 'Something went wrong');
    Flight::redirect('/dashboard');
}