luracast/restler

Restler is a simple and effective multi-format Web API Server framework written in PHP. Just deal with your business logic in php, Restler will take care of the REST!

Installs: 520 214

Dependents: 8

Suggesters: 0

Security: 1

Stars: 1 366

Watchers: 82

Forks: 312

Open Issues: 33

pkg:composer/luracast/restler

6.0.0 2025-12-01 14:01 UTC

README

Latest Stable Version Total Downloads License

Write your API logic in PHP. Get JSON, XML, CSV, Excel, GraphQL, OpenAPI docs, and more—automatically.

Production-ready since 2010. Battle-tested. Now rebuilt for modern PHP 8+ with async superpowers.

class Products {
    function get(int $id): array {
        return Database::findProduct($id);
    }
}

$api = new Restler();
$api->addAPIClass(Products::class);
$api->handle();

That's it. You just created a REST API that:

  • ✅ Automatically validates $id as an integer
  • ✅ Returns JSON by default (.xml, .csv, .yaml also work)
  • ✅ Handles routing (GET /products/123)
  • ✅ Generates OpenAPI/Swagger docs
  • ✅ Provides proper HTTP status codes
  • ✅ Supports content negotiation

No controllers. No routing files. No configuration. Just PHP.

Why Restler in 2025?

🎯 Zero Boilerplate

While other frameworks make you write controllers, routes, DTOs, validation rules, and transformers—Restler uses PHP reflection to do it all automatically. Write business logic, not plumbing.

// Laravel/Symfony: 50+ lines of controllers, routes, requests, resources
// Restler: 5 lines
class Users {
    function create(string $email, string $name): User {
        return User::create(compact('email', 'name'));
    }
}

Async Performance

Run on Swoole/OpenSwoole for 10-20x throughput vs traditional PHP-FPM. Or use ReactPHP for true async I/O. Deploy to AWS Lambda for serverless scale.

# Traditional PHP-FPM: ~1,000 req/sec
# Swoole/OpenSwoole: ~15,000 req/sec (same code!)
composer swoole-server

🌐 Multi-Format by Default

Your API automatically speaks JSON, XML, YAML, CSV, Excel, and HTML. Perfect for:

  • Mobile apps → JSON
  • Legacy systems → XML
  • Data exports → CSV/Excel
  • Admin panels → HTML with Blade/Twig
curl api.example.com/products/123       # JSON
curl api.example.com/products/123.xml   # XML
curl api.example.com/products.csv       # CSV export
curl api.example.com/products.xlsx      # Excel download

🚀 Modern PHP 8+

Built for PHP 8 with attributes, union types, named arguments, and strict typing. PSR-7 and PSR-11 compliant.

use Luracast\Restler\Attribute\{Get, Post};

class Orders {
    #[Get('orders/{id}')]
    function getOrder(int $id): Order|null {
        return Order::find($id);
    }

    #[Post('orders')]
    function createOrder(string $product, int $quantity = 1): Order {
        return Order::create(compact('product', 'quantity'));
    }
}

📚 Auto-Generated Docs

OpenAPI 3.0 (Swagger) docs generated from your PHPDoc comments. Interactive API explorer included.

class Products {
    /**
     * Get product details
     *
     * @param int $id Product ID
     * @return Product product information
     * @throws 404 Product not found
     */
    function get(int $id): Product {
        return Product::findOrFail($id);
    }
}
// Visit /explorer for interactive Swagger UI

Real-World Use Cases

🏢 Internal APIs & Microservices

Perfect for building internal APIs that need to integrate with various systems. Multi-format support means you can serve JSON to your React app and XML to that ancient CRM system—from the same endpoint.

📱 Mobile Backend

Low latency on Swoole, automatic validation, built-in rate limiting, and OAuth2 support. Everything you need for a production mobile backend.

📊 Data Export APIs

Built-in CSV and Excel streaming support. Export millions of rows without running out of memory using generators.

function exportUsers(): Generator {
    foreach (User::cursor() as $user) {
        yield $user->toArray();
    }
}
// GET /users.csv streams all users as CSV
// GET /users.xlsx downloads Excel file

🔗 Legacy System Integration

Need to modernize an old PHP app? Add Restler to get a REST API instantly. Works alongside existing code—no rewrite needed.

Quick Start

Install

composer require luracast/restler:^6.0

Create Your First API (3 files)

1. API Class (api/Hello.php)

<?php
class Hello {
    function sayHello(string $name = 'World'): string {
        return "Hello, $name!";
    }

    function getTime(): array {
        return ['time' => date('Y-m-d H:i:s'), 'timezone' => date_default_timezone_get()];
    }
}

2. Gateway (public/index.php)

<?php
require __DIR__ . '/../vendor/autoload.php';

$api = new Luracast\Restler\Restler();
$api->addAPIClass('Hello');
$api->handle();

3. URL Rewriting (.htaccess or nginx.conf)

# Apache
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php [QSA,L]

Test It

# Start server
php -S localhost:8080 -t public

# Try your API
curl http://localhost:8080/hello/sayHello/Restler
# Output: "Hello, Restler!"

curl http://localhost:8080/hello/getTime
# Output: {"time":"2025-01-15 10:30:45","timezone":"UTC"}

curl http://localhost:8080/hello/getTime.xml
# Output: <?xml version="1.0"?><response><time>2025-01-15 10:30:45</time>...

That's it! You have a working REST API with automatic routing, validation, and multi-format support.

Production Deployment

Traditional (Apache/Nginx + PHP-FPM)

Standard deployment. Works everywhere. ~1,000-2,000 req/sec depending on hardware.

// Enable production mode for route caching
$api = new Restler(productionMode: true);

High Performance (Swoole/OpenSwoole)

10-20x faster than PHP-FPM. Persistent connections, coroutines, built-in HTTP server.

# Install extension (choose one)
pecl install swoole        # Original
pecl install openswoole    # Fork with same API

# Run server
php public/index_swoole.php

Benchmarks: 15,000+ req/sec on modest hardware (vs ~1,000 for PHP-FPM)

Async I/O (ReactPHP)

True non-blocking async operations. Perfect for I/O-heavy workloads (database, HTTP calls, etc.).

composer react-server

Serverless (AWS Lambda)

Zero-downtime deploys, automatic scaling, pay-per-request pricing.

# Uses Bref for Laravel-style Lambda deployment
vendor/bin/bref deploy

See public/index_lambda.php for complete example.

Event-Driven (Workerman)

Alternative to Swoole with pure PHP implementation (no extension required).

php public/index_workerman.php start

What's New in v6

🔥 Breaking Changes from v5

  • PHP 8.0+ required (was PHP 7.4)
  • PSR-7 HTTP messages (modern request/response objects)
  • PSR-11 Container (standard dependency injection)
  • Stricter typing (full PHP 8 type hints)

Migration Guide →

New Features

Modern PHP 8 Support

  • ✅ Attributes (#[Get], #[Post], etc.)
  • ✅ Union types (string|int, User|null)
  • ✅ Named arguments
  • ✅ Constructor property promotion
  • ✅ Match expressions
  • ✅ Enums

Async & Performance

  • Swoole/OpenSwoole integration (10-20x faster)
  • ReactPHP async server
  • Workerman event-driven server
  • AWS Lambda serverless support
  • Generator streaming for large datasets (CSV, Excel)
  • ✅ Route caching & opcode optimization

Enhanced Security

  • ✅ Secure session serialization (JSON, not unserialize())
  • ✅ JSONP callback validation (XSS prevention)
  • ✅ Template injection protection
  • ✅ Configurable CORS with proper defaults
  • ✅ Built-in rate limiting
  • ✅ OAuth 2.0 server support

Developer Experience

  • GraphQL support
  • Excel export (XLSX streaming)
  • OpenAPI 3.0 spec generation
  • ✅ Interactive API Explorer (Swagger UI)
  • ✅ Better error messages
  • Blade, Twig, Mustache template engines
  • ✅ Modern DI container with auto-wiring

Multi-Format Support

All formats work automatically—just add file extension to URL:

  • JSON (default)
  • XML
  • YAML
  • CSV (with streaming)
  • Excel (XLSX)
  • HTML (with templates)
  • AMF (Flash/Flex)
  • Plist (iOS/macOS)

Advanced Examples

API Versioning

// v1/Users.php
namespace v1;
class Users {
    function get(int $id): array {
        return ['id' => $id, 'name' => 'John'];
    }
}

// v2/Users.php
namespace v2;
class Users {
    function get(int $id): User {
        return User::with('profile')->find($id);
    }
}

// index.php
$api->addAPIClass('v1\\Users', 'v1/users');
$api->addAPIClass('v2\\Users', 'v2/users');

Usage:

curl api.example.com/v1/users/123  # Old format
curl api.example.com/v2/users/123  # New format with profile

Authentication & Rate Limiting

use Luracast\Restler\Contracts\AuthenticationInterface;

class ApiKeyAuth implements AuthenticationInterface {
    public function __isAuthenticated(): bool {
        $key = $_SERVER['HTTP_X_API_KEY'] ?? null;
        return $key && ApiKey::validate($key);
    }
}

$api = new Restler();
$api->addAuthenticationClass(ApiKeyAuth::class);
$api->addFilterClass(RateLimit::class);  // Built-in rate limiting
$api->addAPIClass(ProtectedAPI::class);
$api->handle();

Database Integration

class Products {
    /**
     * List all products with pagination
     *
     * @param int $page Page number (default: 1)
     * @param int $limit Items per page (default: 20)
     */
    function index(int $page = 1, int $limit = 20): array {
        return Product::paginate($limit, page: $page)->toArray();
    }

    /**
     * Create new product
     *
     * @param string $name Product name
     * @param float $price Product price
     * @param string $category Category name
     */
    function post(string $name, float $price, string $category): Product {
        return Product::create(compact('name', 'price', 'category'));
    }

    /**
     * Update product
     *
     * @param int $id Product ID {@from path}
     */
    function put(int $id, string $name = null, float $price = null): Product {
        $product = Product::findOrFail($id);
        if ($name) $product->name = $name;
        if ($price) $product->price = $price;
        $product->save();
        return $product;
    }

    /**
     * Delete product
     *
     * @param int $id Product ID {@from path}
     */
    function delete(int $id): array {
        Product::findOrFail($id)->delete();
        return ['message' => 'Product deleted'];
    }
}

Automatic REST endpoints:

  • GET /products → index()
  • POST /products → post()
  • GET /products/123 → (auto-routes to index with $id)
  • PUT /products/123 → put()
  • DELETE /products/123 → delete()

File Uploads

class Media {
    /**
     * Upload file
     *
     * @param array $file Upload file {@from body} {@type file}
     */
    function post(array $file): array {
        $path = Storage::put('uploads', $file['tmp_name']);
        return [
            'filename' => $file['name'],
            'url' => Storage::url($path),
            'size' => $file['size']
        ];
    }
}

Streaming Large Datasets

class Reports {
    /**
     * Export all users (memory efficient)
     */
    function exportUsers(): Generator {
        // Processes millions of rows without memory issues
        foreach (User::cursor() as $user) {
            yield [
                'id' => $user->id,
                'email' => $user->email,
                'created' => $user->created_at
            ];
        }
    }
}

// GET /reports/exportUsers.csv → streams CSV
// GET /reports/exportUsers.xlsx → streams Excel

Custom Routing

class Products {
    /**
     * @url GET products/featured
     */
    function getFeatured(): array {
        return Product::where('featured', true)->get();
    }

    /**
     * @url GET products/search/{query}
     * @param string $query Search term {@from path}
     */
    function search(string $query): array {
        return Product::where('name', 'LIKE', "%$query%")->get();
    }

    /**
     * @url POST products/{id}/publish
     * @param int $id Product ID {@from path}
     */
    function publish(int $id): array {
        $product = Product::findOrFail($id);
        $product->published = true;
        $product->save();
        return ['message' => 'Published successfully'];
    }
}

GraphQL Support

use Luracast\Restler\OpenApi3\Explorer;

$api = new Restler();
$api->addAPIClass(GraphQLEndpoint::class, 'graphql');
$api->addAPIClass(Explorer::class, 'explorer');
$api->handle();

// POST /graphql
// Query and mutation support built-in

CORS Configuration

use Luracast\Restler\Defaults;

Defaults::$accessControlAllowOrigin = 'https://app.example.com';
Defaults::$accessControlAllowMethods = 'GET, POST, PUT, DELETE';
Defaults::$accessControlAllowHeaders = 'Content-Type, Authorization';
Defaults::$accessControlMaxAge = 86400;

$api = new Restler();
$api->addAPIClass(MyAPI::class);
$api->handle();

Interactive API Explorer

Restler includes a built-in Swagger UI explorer:

use Luracast\Restler\OpenApi3\Explorer;

$api = new Restler();
$api->addAPIClass(Products::class);
$api->addAPIClass(Users::class);
$api->addAPIClass(Explorer::class, 'explorer');  // Add explorer
$api->handle();

Visit http://localhost:8080/explorer to:

  • Browse all endpoints
  • See request/response schemas
  • Test APIs interactively
  • Download OpenAPI spec

The explorer is auto-generated from your PHPDoc comments—no manual work needed.

Testing

Restler includes 18+ working examples and a full Behat test suite.

Run Built-in Tests

# Start server
composer serve

# Run tests (in another terminal)
composer test

Results: 310/310 scenarios passing ✅

Write Your Own Tests

# features/products.feature
Feature: Product API

  Scenario: Get product by ID
    When I request "GET /products/123"
    Then the response status code should be 200
    And the response is JSON
    And the response has a "name" property

  Scenario: Create product
    When I request "POST /products" with body:
      """
      {"name": "Widget", "price": 19.99}
      """
    Then the response status code should be 201
    And the response has a "id" property

Example Projects

Try the included examples:

  • _001_helloworld - Minimal API
  • _003_multiformat - JSON, XML, YAML, CSV
  • _005_protected_api - Authentication
  • _007_crud - Full CRUD with database
  • _009_rate_limiting - Rate limiting
  • _011_versioning - API versioning
  • _013_html - HTML responses with templates
  • _015_oauth2_server - OAuth 2.0 server
  • _018_graphql - GraphQL integration
composer serve
# Visit http://localhost:8080/examples/

Performance Tips

1. Enable Production Mode

// Caches routes, disables debug mode
$api = new Restler(productionMode: true);

// Clear route cache after code changes:
// rm cache/routes.php

2. Use Swoole/OpenSwoole

10-20x performance improvement over PHP-FPM. Same code, no changes needed.

pecl install swoole
php public/index_swoole.php

3. Use Generators for Large Datasets

// Bad: Loads everything into memory
function getUsers(): array {
    return User::all()->toArray();  // 💥 100MB+
}

// Good: Streams data
function getUsers(): Generator {
    foreach (User::cursor() as $user) {
        yield $user->toArray();  // ✅ Constant memory
    }
}

4. Cache Expensive Operations

use Luracast\Restler\Defaults;

// Enable route caching
Defaults::$cacheDirectory = __DIR__ . '/cache';

// Use your own caching for data
function getPopularProducts(): array {
    return Cache::remember('popular_products', 3600, function() {
        return Product::orderBy('views', 'desc')->take(10)->get();
    });
}

5. Optimize Database Queries

// Use eager loading to avoid N+1 queries
function index(): array {
    return Order::with(['user', 'items.product'])->get();
}

Framework Comparison

Feature Restler v6 Laravel Symfony Slim
Auto-routing from methods
Multi-format (JSON/XML/CSV/Excel) Partial Partial
Auto OpenAPI docs Via package Via package Via package
Swoole support Via package Via package
Zero config Partial
Lines of code for CRUD API ~20 ~100 ~150 ~50
Learning curve Very Low Medium High Low
Best for APIs Full-stack Enterprise Microservices

Restler Philosophy: Write business logic. Get the REST for free.

Migration from v5

Upgrading from Restler v5? Most code works unchanged. Key changes:

Requirements

  • PHP 8.0+ (was 7.4+)
  • Add PSR-7 and PSR-11 to composer.json (auto-installed)

Type Hints (Recommended)

// v5 - Still works but add types for better validation
public function getUser($id) {
    return User::find($id);
}

// v6 - Recommended
public function getUser(int $id): ?User {
    return User::find($id);
}

Breaking Changes

  • Removed deprecated reflection methods (internal only)
  • Session serialization now uses JSON (more secure)
  • Some internal class reorganization

Full migration guide: MIGRATION.md

Documentation

FAQ

When should I use Restler?

Great for:

  • Internal APIs and microservices
  • Mobile app backends
  • Data export APIs (CSV, Excel)
  • Rapid prototyping
  • Modernizing legacy PHP apps
  • APIs that need multiple formats (JSON + XML + CSV)
  • High-performance requirements (with Swoole)

Not ideal for:

  • Full-stack web apps with server-side rendering (use Laravel/Symfony)
  • GraphQL-first APIs (though GraphQL support is included)
  • Non-PHP environments

How is this different from Laravel/Symfony?

Restler is laser-focused on APIs. Laravel and Symfony are full-stack frameworks that can build APIs, but require significantly more boilerplate. Restler uses reflection to eliminate boilerplate entirely.

Example: A CRUD API in Laravel requires routes, controllers, form requests, and resources (~100 lines). In Restler it's ~20 lines.

Is Swoole support production-ready?

Yes! Swoole has been production-ready since 2018. Used by companies like Alibaba, Tencent, and Baidu. OpenSwoole is a fork with the same stability. Both work identically with Restler.

Does it work with [my favorite ORM/database]?

Yes! Restler is database-agnostic. Use Eloquent, Doctrine, RedBeanPHP, PDO, or anything else. Examples included for all major ORMs.

Can I use it with Docker/Kubernetes?

Absolutely. Dockerfile examples included. Works great in containers, especially with Swoole for high performance.

Is it really production-ready?

Yes! Restler has been used in production since 2010. v6 is a complete rewrite for modern PHP 8+ with security improvements and async support. Currently powers APIs handling millions of requests daily.

What's the performance like?

  • PHP-FPM: ~1,000-2,000 req/sec (typical)
  • Swoole/OpenSwoole: ~15,000-20,000 req/sec (same hardware)
  • AWS Lambda: Automatic scaling, cold start ~100ms

Actual numbers depend on your application logic and hardware.

Support & Community

Contributing

We welcome contributions! Whether it's:

  • 🐛 Bug fixes
  • ✨ New features
  • 📖 Documentation improvements
  • 🧪 Test coverage
  • 💡 Ideas and suggestions

See CONTRIBUTING.md for guidelines.

Development Setup

git clone https://github.com/Luracast/Restler.git
cd Restler
git checkout v6
composer install
composer serve  # Start dev server

# Run tests
composer test

License

Restler is open-source software licensed under the MIT License.

Free for commercial and personal use.

Credits

Created and maintained by Luracast

Special thanks to all contributors who have helped make Restler better over the years!

Ready to build better APIs?

composer require luracast/restler:^6.0

Write PHP. Get REST. 🚀

Get StartedExamplesDocumentationStar on GitHub ⭐