Lifecycle of a Request in Lychee: Album Creation
December 22, 2025 · View on GitHub
This document traces the complete lifecycle of an HTTP request in Lychee, using Creating an Album as a concrete example. Understanding this flow will help developers navigate the codebase and understand how requests are processed from start to finish.
Request Flow Overview
1. Frontend (Vue.js) → 2. Route → 3. Middleware → 4. Request Validation → 5. Controller → 6. Action → 7. Model → 8. Database → 9. Response Resource → 10. Frontend
Example: Creating an Album
Let's trace a POST /Album request to create a new album.
1. Frontend Request (Vue.js)
The lifecycle begins when a user clicks "Create Album" in the Vue.js frontend:
// Frontend makes API call using AlbumService
import AlbumService, { type CreateAlbumData } from '@/services/album-service';
const albumData: CreateAlbumData = {
title: 'My New Album',
parent_id: 'parent-album-id-or-null' // or null for root level
};
AlbumService.createAlbum(albumData)
.then((response) => {
// Handle successful album creation
const albumId = response.data; // Returns the new album ID
})
.catch((error) => {
// Handle error
});
Under the hood, AlbumService.createAlbum() uses Axios:
// File: resources/js/services/album-service.ts
createAlbum(data: CreateAlbumData): Promise<AxiosResponse<string>> {
return axios.post(`${Constants.getApiUrl()}Album`, data);
}
2. Route Resolution
Laravel's router resolves the request using the route definition:
// File: routes/api_v2.php
Route::post('/Album', [Gallery\AlbumController::class, 'createAlbum']);
What happens:
- Router matches the HTTP method (
POST) and path (/Album) - Router identifies the target controller and method:
AlbumController@createAlbum - Route parameters and middleware are collected
3. Middleware Pipeline
Before reaching the controller, the request passes through Laravel's middleware pipeline:
Global Middleware Stack
// File: app/Http/Kernel.php
protected $middleware = [
\App\Http\Middleware\TrustProxies::class,
\Illuminate\Http\Middleware\HandleCors::class,
\App\Http\Middleware\CheckForMaintenanceMode::class,
// ... more middleware
];
Route-Specific Middleware
// Applied via route groups in routes/api_v2.php
->middleware(['api', 'content_type:json', 'accept_content_type:json'])
Key middleware actions:
- CSRF Protection: Validates CSRF token
- Authentication: Checks if user is logged in
- Content Type: Ensures JSON content-type headers
- Rate Limiting: Prevents abuse (if configured)
4. Request Validation & Authorization
Laravel creates a Request object and validates it:
// File: app/Http/Requests/Album/AddAlbumRequest.php
class AddAlbumRequest extends BaseApiRequest implements HasTitle, HasParentAlbum
{
public function authorize(): bool
{
return Gate::check(AlbumPolicy::CAN_EDIT, [AbstractAlbum::class, $this->parent_album]);
}
public function rules(): array
{
return [
RequestAttribute::TITLE_ATTRIBUTE => ['required', new TitleRule()],
RequestAttribute::PARENT_ID_ATTRIBUTE => ['sometimes', new AlbumIDRule(true)],
];
}
protected function processValidatedValues(array $values, array $files): void
{
$this->title = $values[RequestAttribute::TITLE_ATTRIBUTE];
// Process parent album if provided...
}
}
What happens:
- Input Validation: Validates title is required and follows TitleRule
- Data Processing: Extracts and processes validated values
- Authorization: Checks if user can edit the parent album (or root if null)
- Request Object Population: Creates strongly-typed request object
Validation Failure: If validation fails, Laravel returns a 422 Unprocessable Entity response with error details.
5. Controller Method Execution
The validated request reaches the controller:
// File: app/Http/Controllers/Gallery/AlbumController.php
public function createAlbum(AddAlbumRequest $request): string
{
// Dispatch cache update event
AlbumRouteCacheUpdated::dispatch($request->parent_album?->id ?? '');
// Get authenticated user
$owner_id = Auth::id() ?? throw new UnauthenticatedException();
// Create action instance
$create = new Create($owner_id);
// Execute business logic and return album ID
return $create->create($request->title(), $request->parent_album())->id;
}
Controller responsibilities:
- Minimal business logic (delegation pattern)
- Event dispatching for cache invalidation
- User authentication verification
- Action instantiation and execution
- Response formatting
6. Action Pattern Execution
Business logic is encapsulated in Action classes:
// File: app/Actions/Album/Create.php
class Create
{
public function __construct(public readonly int $intended_owner_id) {}
public function create(string $title, ?Album $parent_album): Album
{
// 1. Create new Album model
$album = new Album();
$album->title = $title;
// 2. Set parent-child relationship
$this->set_parent($album, $parent_album);
// 3. Save to database
$album->save();
// 4. Set up permissions
$this->set_permissions($album, $parent_album);
// 5. Initialize statistics
$this->setStatistics($album);
return $album;
}
private function set_parent(Album $album, ?Album $parent_album): void
{
if ($parent_album !== null) {
$album->owner_id = $parent_album->owner_id;
$album->appendToNode($parent_album); // Nested set pattern
} else {
$album->owner_id = $this->intended_owner_id;
$album->makeRoot();
}
}
private function set_permissions(Album $album, ?Album $parent_album): void
{
// Create access permissions for the album owner
$access_perm = AccessPermission::withGrantFullPermissionsToUser($this->intended_owner_id);
$album->access_permissions()->save($access_perm);
}
private function setStatistics(Album $album): void
{
$album->statistics()->create([
'album_id' => $album->id,
'visit_count' => 0,
'download_count' => 0,
'favourite_count' => 0,
'shared_count' => 0,
]);
}
}
Action Pattern Benefits:
- Single Responsibility: Each action handles one business operation
- Testability: Actions can be unit tested independently
- Reusability: Actions can be used from multiple controllers
- Clarity: Business logic is explicit and well-organized
7. Model Interactions
The Action interacts with Eloquent models:
// File: app/Models/Album.php
class Album extends BaseAlbum
{
use HasFactory, ThrowsConsistentExceptions, UTCBasedTimes;
use NodeTrait; // For nested set tree structure
protected $fillable = [
'title', 'description', 'owner_id', 'parent_id'
// ... more attributes
];
// Relationships
public function owner(): BelongsTo { /* ... */ }
public function photos(): HasMany { /* ... */ }
public function access_permissions(): HasMany { /* ... */ }
public function statistics(): HasOne { /* ... */ }
}
Model Operations:
- Attribute Assignment: Title, owner_id, etc.
- Tree Structure: Using Laravel Nested Set for hierarchical albums
- Relationship Creation: Access permissions and statistics
- Database Persistence: Multiple SQL queries executed
8. Database Operations
Several database operations occur during album creation:
-- 1. Insert new album record
INSERT INTO albums (id, title, owner_id, parent_id, _lft, _rgt, created_at, updated_at)
VALUES ('uuid-generated', 'My New Album', 1, NULL, 1, 2, NOW(), NOW());
-- 2. Update nested set tree structure (if has parent)
UPDATE albums SET _rgt = _rgt + 2 WHERE _rgt >= ? AND id != ?;
UPDATE albums SET _lft = _lft + 2 WHERE _lft > ? AND id != ?;
-- 3. Create access permissions
INSERT INTO access_permissions (album_id, user_id, grants_full_photo_access, ...)
VALUES ('album-uuid', 1, 1, ...);
-- 4. Create statistics record
INSERT INTO album_statistics (album_id, visit_count, download_count, ...)
VALUES ('album-uuid', 0, 0, ...);
9. Response Formation
The controller returns the album ID, which Laravel automatically converts to JSON:
// Controller returns: string (album ID)
return $create->create($request->title(), $request->parent_album())->id;
// Laravel converts to HTTP response:
HTTP/1.1 200 OK
Content-Type: application/json
"album-uuid-here"
Response Handling:
- Automatic Serialization: Laravel converts return value to JSON
- HTTP Status Code: 200 OK for successful creation
- Headers: Content-Type, CSRF tokens, etc.
10. Frontend Processing
The Vue.js frontend receives and processes the response through the AlbumService:
// Using the AlbumService
AlbumService.createAlbum(albumData)
.then(response => {
const albumId = response.data; // New album ID returned
// Clear album cache to refresh listings
AlbumService.clearAlbums();
// Update local state (using Pinia store)
albumStore.addNewAlbum({
id: albumId,
title: albumData.title,
parent_id: albumData.parent_id,
// ... other properties
});
// Navigate to new album or update UI
router.push(`/gallery/${albumId}`);
// Show success notification
notify.success('Album created successfully!');
})
.catch(error => {
// Handle different error types
if (error.response?.status === 422) {
// Validation errors
const errors = error.response.data.errors;
handleValidationErrors(errors);
} else {
// Other errors
notify.error('Failed to create album');
}
});
Key Frontend Features:
- Type Safety: TypeScript interfaces ensure correct data structure
- Cache Management: Automatic cache clearing for updated album listings
- Error Handling: Specific handling for validation vs server errors
- State Management: Integration with Pinia stores for reactive UI updates
Error Handling Throughout the Lifecycle
Validation Errors (422 Unprocessable Entity)
{
"message": "The given data was invalid.",
"errors": {
"title": ["The title field is required."]
}
}
Authorization Errors (403 Forbidden)
{
"message": "Unauthorized action."
}
Server Errors (500 Internal Server Error)
{
"message": "Internal server error occurred."
}
Event System Integration
Throughout the lifecycle, events are dispatched for cross-cutting concerns:
// Cache invalidation
AlbumRouteCacheUpdated::dispatch($album->id);
// Metrics tracking (if enabled)
AlbumCreated::dispatch($album);
Security Measures
Input Sanitization
- XSS Prevention: HTML escaping in templates
- SQL Injection Prevention: Eloquent ORM parameterized queries
Access Control
- Authentication: User identity verification
- Authorization: Permission-based access control
- CSRF Protection: Token validation
Testing the Lifecycle
Feature Tests
// File: tests/Feature/CreateAlbumTest.php
public function test_user_can_create_album()
{
$user = User::factory()->create();
$response = $this->actingAs($user)
->postJson('/api/v2/Album', [
'title' => 'Test Album',
'parent_id' => null
]);
$response->assertStatus(200);
$this->assertDatabaseHas('albums', [
'title' => 'Test Album',
'owner_id' => $user->id
]);
}
Summary
The request lifecycle in Lychee follows a clean, layered architecture:
- Presentation Layer: Vue.js frontend handles user interaction
- HTTP Layer: Laravel routing and middleware handle request processing
- Validation Layer: Request classes ensure data integrity and authorization
- Controller Layer: Minimal logic, delegates to actions
- Business Logic Layer: Actions encapsulate domain operations
- Data Layer: Eloquent models interact with database
- Response Layer: Resources format and return data
This architecture provides:
- Separation of Concerns: Each layer has specific responsibilities
- Testability: Individual components can be tested in isolation
- Maintainability: Clear structure makes code changes predictable
- Security: Multiple layers of validation and authorization
- Performance: Optimized database operations and caching strategies
Understanding this flow helps developers know where to make changes for different types of features and ensures consistency with Lychee's architectural patterns.
Last updated: December 22, 2025