coolify/.ai/patterns/api-and-routing.md
Andras Bacsai 3f7c5fbdf9 Consolidate AI documentation into .ai/ directory
- Create .ai/ directory as single source of truth for all AI docs
- Organize by topic: core/, development/, patterns/, meta/
- Update CLAUDE.md to reference .ai/ files instead of embedding content
- Remove 18KB of duplicated Laravel Boost guidelines from CLAUDE.md
- Fix testing command descriptions (pest runs all tests, not just unit)
- Standardize version numbers (Laravel 12.4.1, PHP 8.4.7, Tailwind 4.1.4)
- Replace all .cursor/rules/*.mdc with single coolify-ai-docs.mdc reference
- Delete dev_workflow.mdc (non-Coolify Task Master content)
- Merge cursor_rules.mdc + self_improve.mdc into maintaining-docs.md
- Update .AI_INSTRUCTIONS_SYNC.md to redirect to new location

Benefits:
- Single source of truth - no more duplication
- Consistent versions across all documentation
- Better organization by topic
- Platform-agnostic .ai/ directory works for all AI tools
- Reduced CLAUDE.md from 719 to ~320 lines
- Clear cross-references between files
2025-11-18 14:58:59 +01:00

14 KiB

Coolify API & Routing Architecture

Routing Structure

Coolify implements multi-layered routing with web interfaces, RESTful APIs, webhook endpoints, and real-time communication channels.

Route Files

Core Route Definitions

Web Application Routing

Authentication Routes

// Laravel Fortify authentication
Route::middleware('guest')->group(function () {
    Route::get('/login', [AuthController::class, 'login']);
    Route::get('/register', [AuthController::class, 'register']);
    Route::get('/forgot-password', [AuthController::class, 'forgotPassword']);
});

Dashboard & Core Features

// Main application routes
Route::middleware(['auth', 'verified'])->group(function () {
    Route::get('/dashboard', Dashboard::class)->name('dashboard');
    Route::get('/projects', ProjectIndex::class)->name('projects');
    Route::get('/servers', ServerIndex::class)->name('servers');
    Route::get('/teams', TeamIndex::class)->name('teams');
});

Resource Management Routes

// Server management
Route::prefix('servers')->group(function () {
    Route::get('/{server}', ServerShow::class)->name('server.show');
    Route::get('/{server}/edit', ServerEdit::class)->name('server.edit');
    Route::get('/{server}/logs', ServerLogs::class)->name('server.logs');
});

// Application management
Route::prefix('applications')->group(function () {
    Route::get('/{application}', ApplicationShow::class)->name('application.show');
    Route::get('/{application}/deployments', ApplicationDeployments::class);
    Route::get('/{application}/environment-variables', ApplicationEnvironmentVariables::class);
    Route::get('/{application}/logs', ApplicationLogs::class);
});

RESTful API Architecture

API Versioning

// API route structure
Route::prefix('v1')->group(function () {
    // Application endpoints
    Route::apiResource('applications', ApplicationController::class);
    Route::apiResource('servers', ServerController::class);
    Route::apiResource('teams', TeamController::class);
});

Authentication & Authorization

// Sanctum API authentication
Route::middleware('auth:sanctum')->group(function () {
    Route::get('/user', function (Request $request) {
        return $request->user();
    });
    
    // Team-scoped resources
    Route::middleware('team.access')->group(function () {
        Route::apiResource('applications', ApplicationController::class);
    });
});

Application Management API

// Application CRUD operations
Route::prefix('applications')->group(function () {
    Route::get('/', [ApplicationController::class, 'index']);
    Route::post('/', [ApplicationController::class, 'store']);
    Route::get('/{application}', [ApplicationController::class, 'show']);
    Route::patch('/{application}', [ApplicationController::class, 'update']);
    Route::delete('/{application}', [ApplicationController::class, 'destroy']);
    
    // Deployment operations
    Route::post('/{application}/deploy', [ApplicationController::class, 'deploy']);
    Route::post('/{application}/restart', [ApplicationController::class, 'restart']);
    Route::post('/{application}/stop', [ApplicationController::class, 'stop']);
    Route::get('/{application}/logs', [ApplicationController::class, 'logs']);
});

Server Management API

// Server operations
Route::prefix('servers')->group(function () {
    Route::get('/', [ServerController::class, 'index']);
    Route::post('/', [ServerController::class, 'store']);
    Route::get('/{server}', [ServerController::class, 'show']);
    Route::patch('/{server}', [ServerController::class, 'update']);
    Route::delete('/{server}', [ServerController::class, 'destroy']);
    
    // Server actions
    Route::post('/{server}/validate', [ServerController::class, 'validate']);
    Route::get('/{server}/usage', [ServerController::class, 'usage']);
    Route::post('/{server}/cleanup', [ServerController::class, 'cleanup']);
});

Database Management API

// Database operations
Route::prefix('databases')->group(function () {
    Route::get('/', [DatabaseController::class, 'index']);
    Route::post('/', [DatabaseController::class, 'store']);
    Route::get('/{database}', [DatabaseController::class, 'show']);
    Route::patch('/{database}', [DatabaseController::class, 'update']);
    Route::delete('/{database}', [DatabaseController::class, 'destroy']);
    
    // Database actions
    Route::post('/{database}/backup', [DatabaseController::class, 'backup']);
    Route::post('/{database}/restore', [DatabaseController::class, 'restore']);
    Route::get('/{database}/logs', [DatabaseController::class, 'logs']);
});

Webhook Architecture

Git Integration Webhooks

// GitHub webhook endpoints
Route::post('/webhooks/github/{application}', [GitHubWebhookController::class, 'handle'])
    ->name('webhooks.github');

// GitLab webhook endpoints
Route::post('/webhooks/gitlab/{application}', [GitLabWebhookController::class, 'handle'])
    ->name('webhooks.gitlab');

// Generic Git webhooks
Route::post('/webhooks/git/{application}', [GitWebhookController::class, 'handle'])
    ->name('webhooks.git');

Deployment Webhooks

// Deployment status webhooks
Route::post('/webhooks/deployment/{deployment}/success', [DeploymentWebhookController::class, 'success']);
Route::post('/webhooks/deployment/{deployment}/failure', [DeploymentWebhookController::class, 'failure']);
Route::post('/webhooks/deployment/{deployment}/progress', [DeploymentWebhookController::class, 'progress']);

Third-Party Integration Webhooks

// Monitoring webhooks
Route::post('/webhooks/monitoring/{server}', [MonitoringWebhookController::class, 'handle']);

// Backup status webhooks
Route::post('/webhooks/backup/{backup}', [BackupWebhookController::class, 'handle']);

// SSL certificate webhooks
Route::post('/webhooks/ssl/{certificate}', [SslWebhookController::class, 'handle']);

WebSocket Channel Definitions

Real-Time Channels

// Private channels for team members
Broadcast::channel('team.{teamId}', function ($user, $teamId) {
    return $user->teams->contains('id', $teamId);
});

// Application deployment channels
Broadcast::channel('application.{applicationId}', function ($user, $applicationId) {
    return $user->hasAccessToApplication($applicationId);
});

// Server monitoring channels
Broadcast::channel('server.{serverId}', function ($user, $serverId) {
    return $user->hasAccessToServer($serverId);
});

Presence Channels

// Team collaboration presence
Broadcast::channel('team.{teamId}.presence', function ($user, $teamId) {
    if ($user->teams->contains('id', $teamId)) {
        return ['id' => $user->id, 'name' => $user->name];
    }
});

API Controllers

Location: app/Http/Controllers/Api/

Resource Controllers

class ApplicationController extends Controller
{
    public function index(Request $request)
    {
        return ApplicationResource::collection(
            $request->user()->currentTeam->applications()
                ->with(['server', 'environment'])
                ->paginate()
        );
    }
    
    public function store(StoreApplicationRequest $request)
    {
        $application = $request->user()->currentTeam
            ->applications()
            ->create($request->validated());
            
        return new ApplicationResource($application);
    }
    
    public function deploy(Application $application)
    {
        $deployment = $application->deploy();
        
        return response()->json([
            'message' => 'Deployment started',
            'deployment_id' => $deployment->id
        ]);
    }
}

API Responses & Resources

// API Resource classes
class ApplicationResource extends JsonResource
{
    public function toArray($request)
    {
        return [
            'id' => $this->id,
            'name' => $this->name,
            'fqdn' => $this->fqdn,
            'status' => $this->status,
            'git_repository' => $this->git_repository,
            'git_branch' => $this->git_branch,
            'created_at' => $this->created_at,
            'updated_at' => $this->updated_at,
            'server' => new ServerResource($this->whenLoaded('server')),
            'environment' => new EnvironmentResource($this->whenLoaded('environment')),
        ];
    }
}

API Authentication

Sanctum Token Authentication

// API token generation
Route::post('/auth/tokens', function (Request $request) {
    $request->validate([
        'name' => 'required|string',
        'abilities' => 'array'
    ]);
    
    $token = $request->user()->createToken(
        $request->name,
        $request->abilities ?? []
    );
    
    return response()->json([
        'token' => $token->plainTextToken,
        'abilities' => $token->accessToken->abilities
    ]);
});

Team-Based Authorization

// Team access middleware
class EnsureTeamAccess
{
    public function handle($request, Closure $next)
    {
        $teamId = $request->route('team');
        
        if (!$request->user()->teams->contains('id', $teamId)) {
            abort(403, 'Access denied to team resources');
        }
        
        return $next($request);
    }
}

Rate Limiting

API Rate Limits

// API throttling configuration
RateLimiter::for('api', function (Request $request) {
    return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip());
});

// Deployment rate limiting
RateLimiter::for('deployments', function (Request $request) {
    return Limit::perMinute(10)->by($request->user()->id);
});

Webhook Rate Limiting

// Webhook throttling
RateLimiter::for('webhooks', function (Request $request) {
    return Limit::perMinute(100)->by($request->ip());
});

Route Model Binding

Custom Route Bindings

// Custom model binding for applications
Route::bind('application', function ($value) {
    return Application::where('uuid', $value)
        ->orWhere('id', $value)
        ->firstOrFail();
});

// Team-scoped model binding
Route::bind('team_application', function ($value, $route) {
    $teamId = $route->parameter('team');
    return Application::whereHas('environment.project', function ($query) use ($teamId) {
        $query->where('team_id', $teamId);
    })->findOrFail($value);
});

API Documentation

OpenAPI Specification

Documentation Generation

// Swagger/OpenAPI annotations
/**
 * @OA\Get(
 *     path="/api/v1/applications",
 *     summary="List applications",
 *     tags={"Applications"},
 *     security={{"bearerAuth":{}}},
 *     @OA\Response(
 *         response=200,
 *         description="List of applications",
 *         @OA\JsonContent(type="array", @OA\Items(ref="#/components/schemas/Application"))
 *     )
 * )
 */

Error Handling

API Error Responses

// Standardized error response format
class ApiExceptionHandler
{
    public function render($request, Throwable $exception)
    {
        if ($request->expectsJson()) {
            return response()->json([
                'message' => $exception->getMessage(),
                'error_code' => $this->getErrorCode($exception),
                'timestamp' => now()->toISOString()
            ], $this->getStatusCode($exception));
        }
        
        return parent::render($request, $exception);
    }
}

Validation Error Handling

// Form request validation
class StoreApplicationRequest extends FormRequest
{
    public function rules()
    {
        return [
            'name' => 'required|string|max:255',
            'git_repository' => 'required|url',
            'git_branch' => 'required|string',
            'server_id' => 'required|exists:servers,id',
            'environment_id' => 'required|exists:environments,id'
        ];
    }
    
    public function failedValidation(Validator $validator)
    {
        throw new HttpResponseException(
            response()->json([
                'message' => 'Validation failed',
                'errors' => $validator->errors()
            ], 422)
        );
    }
}

Real-Time API Integration

WebSocket Events

// Broadcasting deployment events
class DeploymentStarted implements ShouldBroadcast
{
    public $application;
    public $deployment;
    
    public function broadcastOn()
    {
        return [
            new PrivateChannel("application.{$this->application->id}"),
            new PrivateChannel("team.{$this->application->team->id}")
        ];
    }
    
    public function broadcastWith()
    {
        return [
            'deployment_id' => $this->deployment->id,
            'status' => 'started',
            'timestamp' => now()
        ];
    }
}

API Event Streaming

// Server-Sent Events for real-time updates
Route::get('/api/v1/applications/{application}/events', function (Application $application) {
    return response()->stream(function () use ($application) {
        while (true) {
            $events = $application->getRecentEvents();
            foreach ($events as $event) {
                echo "data: " . json_encode($event) . "\n\n";
            }
            usleep(1000000); // 1 second
        }
    }, 200, [
        'Content-Type' => 'text/event-stream',
        'Cache-Control' => 'no-cache',
    ]);
});