frootbox/rest-api

Frootbox REST API Framework

Maintainers

Package info

github.com/Frootbox/RestApi

pkg:composer/frootbox/rest-api

Transparency log

Statistics

Installs: 700

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

0.7.1 2026-08-07 18:19 UTC

This package is auto-updated.

Last update: 2026-08-07 18:19:36 UTC


README

A lightweight, attribute-based REST API framework for PHP.

This package provides a simple way to build versioned REST APIs with support for multiple authentication methods like API keys, Bearer tokens, Basic Auth, and custom client credentials.

✨ Features

  • Attribute-based routing (OpenAPI compatible)
  • API versioning via namespace (V1, V2, ...)
  • Multiple authentication methods:
    • API Key
    • Bearer (JWT)
    • Basic Auth
    • Client credentials
  • Dependency Injection support (PHP-DI)
  • Automatic route discovery
  • Named route parameters ({id}, {int:id})
  • JSON and application/x-www-form-urlencoded request body parsing
  • JSON response handling with optional response headers/status codes

πŸ“¦ Installation

composer require frootbox/restapi

πŸš€ Getting Started

1. Create a Server instance

use Frootbox\\RestApi\\Server;
use DI\\Container;

$container = new Container();

$server = new Server(
    clientRepository: $clientRepository, // or null when no client authentication is used
    baseUriRegex: '#^/api/v(?P<Version>[0-9]+)(?P<Path>/.*)$#',
    controllerDirectory: __DIR__ . '/Controller',
    namespace: 'App\\\\Controller',
    container: $container,
    hashKey: 'your-secret-key'
);

$server->execute();

πŸ“ Controller Structure

Controllers must follow a versioned namespace structure:

src/
└── Controller/
    └── V1/
        └── UserController.php

🧩 Example Controller

namespace App\\Controller\\V1;

use OpenApi\\Attributes as OA;
use Frootbox\\RestApi\\Attribute\\Auth;
use Frootbox\\RestApi\\Attribute\\ApiKey;
use Frootbox\\RestApi\\Response\\Payload;

class UserController
{
    #[OA\\Get(path: '/users/{int:id}')]
    #[Auth(type: new ApiKey())]
    public function getUser(int $id): Payload
    {
        return new Payload([
            'id' => $id,
            'name' => 'John Doe'
        ]);
    }
}

πŸ” Authentication

You can define one or multiple authentication methods per endpoint:

#[Auth(type: new ApiKey())]
#[Auth(type: new Bearer())]

Supported Auth Methods

Public Client

Use this for browser and native applications which can identify themselves but cannot keep a client secret confidential:

#[Auth(type: new PublicClient())]

Send the public identifier as client_id in the request body. Legacy query string transport follows the allowClientCredentialsInQuery setting. Public clients never send a client_secret.

API Key

Send via header:

x-api-key: your-api-key

Bearer Token (JWT)

Authorization: Bearer <token>

Basic Auth

Authorization: Basic base64(clientId:clientSecret)

Client Credentials (Basic, form body, or legacy query)

Authorization: Basic base64(clientId:clientSecret)

For OAuth-compatible token endpoints, client credentials can also be sent in an application/x-www-form-urlencoded request body as client_id and client_secret. Query string credentials are still supported as a legacy fallback and can be disabled through the Server constructor:

$server = new Server(
    // ...
    allowClientCredentialsInQuery: false
);

Request Payloads

Frootbox\RestApi\Payload supports JSON request bodies and application/x-www-form-urlencoded request bodies. Existing JSON endpoints keep working as before, while OAuth token endpoints can follow the OAuth standard:

POST /oauth/token
Authorization: Basic base64(clientId:clientSecret)
Content-Type: application/x-www-form-urlencoded

grant_type=refresh_token&refresh_token=...

🧠 Client Validation

For confidential client credentials and API keys, provide a repository implementing:

Frootbox\\RestApi\\Interface\\ClientRepositoryInterface

Example:

class ClientRepository implements ClientRepositoryInterface
{
    public function validate(string $clientId, string $clientSecret): void
    {
        if ($clientId !== 'test' || $clientSecret !== 'secret') {
            throw new \\Exception('Invalid client credentials');
        }
    }

    public function validateApiKey(string $apiKey): void
    {
        if ($apiKey !== 'abc123') {
            throw new \\Exception('Invalid API key');
        }
    }
}

Public clients use the opt-in interface below. This keeps existing client repository implementations backwards compatible:

class ClientRepository implements PublicClientRepositoryInterface
{
    public function validatePublicClient(string $clientId, ?callable $onValidateClient = null): void
    {
        $client = $this->findActivePublicClient($clientId);

        if ($client === null) {
            throw new \Exception('Invalid public client');
        }

        if ($onValidateClient !== null) {
            $onValidateClient($client);
        }
    }
}

When an API only uses Bearer and None, pass clientRepository: null. The repository is only required when a route declares Client, BasicAuth, ApiKey, or PublicClient authentication.

πŸ”„ Versioning

API version is extracted from the URL:

/api/v1/users/1

Your controllers must match the version namespace:

namespace App\\Controller\\V1;

🧾 Route Parameters

Integer parameter

/users/{int:id}

String parameter

/users/{slug}

ULID parameter

/users/{ulid:id}

OpenAPI parameter pattern

#[OA\Get(path: '/users/{id}')]
#[OA\Parameter(
    name: 'id',
    in: 'path',
    required: true,
    schema: new OA\Schema(type: 'string', pattern: '^[0-9A-HJKMNP-TV-Z]{26}$'),
)]

Static routes are matched before dynamic routes, so /users/search is preferred over /users/{id} regardless of reflection order.

Parameters are automatically injected into the method.

πŸ“€ Responses

All responses must return:

Frootbox\\RestApi\\Response\\Payload

Example:

return new Payload([
    'success' => true
]);

βš™οΈ Dependency Injection

Controllers are resolved via the provided DI container:

$container->call([$controller, $method]);

You can inject services directly into controller methods:

public function getUser(UserService $service, int $id)

πŸ”§ Hooks

Token decoding hook

$server = new Server(..., onDecodeToken: function ($token) {
    // custom logic
});

Client validation hook

$server = new Server(..., onValidateClient: function ($clientId) {
    // custom logic
});

⚠️ Important Notes

  • Always use HTTPS when transmitting credentials
  • API keys should be treated like passwords
  • Bearer tokens are validated using HS256
  • Route matching is case-insensitive

πŸ“„ License

MIT """