Skip to content

File Structure Standards

Overview

This document defines the standardized file and directory structure for backend projects. Following these conventions ensures consistency, maintainability, and ease of navigation across the codebase.

Project Root Structure

project-root/
├── app/
├── bootstrap/
├── config/
├── database/
├── public/
├── resources/
├── routes/
├── storage/
├── tests/
├── vendor/
├── .env
├── .env.example
├── .gitignore
├── artisan
├── composer.json
├── composer.lock
├── package.json
├── phpunit.xml
└── README.md

App Directory Structure

The app/ directory contains the core application code:

app/
├── Console/
│   ├── Commands/
│   └── Kernel.php
├── Exceptions/
│   └── Handler.php
├── Http/
│   ├── Controllers/
│   │   ├── Api/
│   │   │   ├── V1/
│   │   │   │   ├── UserController.php
│   │   │   │   ├── PostController.php
│   │   │   │   └── AuthController.php
│   │   │   └── V2/
│   │   └── Web/
│   ├── Middleware/
│   │   ├── Authenticate.php
│   │   ├── CheckRole.php
│   │   └── RateLimitMiddleware.php
│   ├── Requests/
│   │   ├── Auth/
│   │   │   ├── LoginRequest.php
│   │   │   └── RegisterRequest.php
│   │   └── User/
│   │       ├── StoreUserRequest.php
│   │       └── UpdateUserRequest.php
│   ├── Resources/
│   │   ├── UserResource.php
│   │   ├── PostResource.php
│   │   └── UserCollection.php
│   └── Kernel.php
├── Models/
│   ├── User.php
│   ├── Post.php
│   ├── Comment.php
│   └── Traits/
│       ├── HasUuid.php
│       └── Searchable.php
├── Providers/
│   ├── AppServiceProvider.php
│   ├── AuthServiceProvider.php
│   ├── EventServiceProvider.php
│   └── RouteServiceProvider.php
├── Services/
│   ├── Auth/
│   │   ├── AuthService.php
│   │   └── TokenService.php
│   ├── User/
│   │   └── UserService.php
│   └── Notification/
│       └── NotificationService.php
├── Repositories/
│   ├── Contracts/
│   │   ├── UserRepositoryInterface.php
│   │   └── PostRepositoryInterface.php
│   ├── UserRepository.php
│   └── PostRepository.php
├── Events/
│   ├── UserCreated.php
│   └── PostPublished.php
├── Listeners/
│   ├── SendWelcomeEmail.php
│   └── NotifyAdmins.php
├── Jobs/
│   ├── ProcessUserImport.php
│   └── SendEmailNotification.php
├── Mail/
│   ├── WelcomeEmail.php
│   └── PasswordReset.php
├── Notifications/
│   ├── UserRegistered.php
│   └── PostPublished.php
├── Policies/
│   ├── UserPolicy.php
│   └── PostPolicy.php
├── Rules/
│   ├── ValidUsername.php
│   └── StrongPassword.php
└── Helpers/
    ├── DateHelper.php
    └── StringHelper.php

Directory Responsibilities

Console

Contains Artisan commands and the console kernel.

app/Console/
├── Commands/
│   ├── GenerateReport.php
│   ├── CleanupOldData.php
│   └── SyncExternalData.php
└── Kernel.php

Naming Convention:

  • Use descriptive verb-noun combinations
  • Suffix with appropriate action: Generate, Cleanup, Sync, Process

Example:

php
// app/Console/Commands/GenerateMonthlyReport.php
namespace App\Console\Commands;

use Illuminate\Console\Command;

class GenerateMonthlyReport extends Command
{
    protected $signature = 'report:generate-monthly';
    protected $description = 'Generate monthly analytics report';

    public function handle()
    {
        // Implementation
    }
}

Exceptions

Custom exception classes for specific error handling.

app/Exceptions/
├── Handler.php
├── Api/
│   ├── ApiException.php
│   ├── ResourceNotFoundException.php
│   └── ValidationException.php
└── Domain/
    ├── UserNotFoundException.php
    └── InsufficientPermissionsException.php

Example:

php
// app/Exceptions/Api/ResourceNotFoundException.php
namespace App\Exceptions\Api;

use Exception;

class ResourceNotFoundException extends Exception
{
    protected $message = 'The requested resource was not found';
    protected $code = 404;
}

HTTP Controllers

Controllers organized by API version and purpose.

app/Http/Controllers/
├── Api/
│   ├── V1/
│   │   ├── Auth/
│   │   │   ├── LoginController.php
│   │   │   ├── RegisterController.php
│   │   │   └── PasswordController.php
│   │   ├── User/
│   │   │   ├── UserController.php
│   │   │   └── UserProfileController.php
│   │   └── Post/
│   │       ├── PostController.php
│   │       └── PostCommentController.php
│   └── V2/
│       └── UserController.php
└── Controller.php (Base Controller)

Naming Conventions:

  • Use singular resource names: UserController, not UsersController
  • Use descriptive names for nested resources: PostCommentController
  • Group related controllers in subdirectories

Example:

php
// app/Http/Controllers/Api/V1/UserController.php
namespace App\Http\Controllers\Api\V1;

use App\Http\Controllers\Controller;
use App\Http\Requests\User\StoreUserRequest;
use App\Services\User\UserService;

class UserController extends Controller
{
    public function __construct(
        private UserService $userService
    ) {}

    public function index() { }
    public function store(StoreUserRequest $request) { }
    public function show($id) { }
    public function update(UpdateUserRequest $request, $id) { }
    public function destroy($id) { }
}

HTTP Requests

Form request classes for validation.

app/Http/Requests/
├── Auth/
│   ├── LoginRequest.php
│   ├── RegisterRequest.php
│   └── ResetPasswordRequest.php
├── User/
│   ├── StoreUserRequest.php
│   ├── UpdateUserRequest.php
│   └── UpdateProfileRequest.php
└── Post/
    ├── StorePostRequest.php
    └── UpdatePostRequest.php

Naming Convention:

  • Prefix with action: Store, Update, Delete
  • Suffix with Request

Example:

php
// app/Http/Requests/User/StoreUserRequest.php
namespace App\Http\Requests\User;

use Illuminate\Foundation\Http\FormRequest;

class StoreUserRequest extends FormRequest
{
    public function authorize(): bool
    {
        return true;
    }

    public function rules(): array
    {
        return [
            'name' => 'required|string|max:255',
            'email' => 'required|email|unique:users',
            'password' => 'required|string|min:8|confirmed',
        ];
    }
}

HTTP Resources

API resource transformers for consistent response formatting.

app/Http/Resources/
├── User/
│   ├── UserResource.php
│   ├── UserCollection.php
│   └── UserProfileResource.php
├── Post/
│   ├── PostResource.php
│   ├── PostCollection.php
│   └── PostDetailResource.php
└── Comment/
    └── CommentResource.php

Example:

php
// app/Http/Resources/User/UserResource.php
namespace App\Http\Resources\User;

use Illuminate\Http\Resources\Json\JsonResource;

class UserResource extends JsonResource
{
    public function toArray($request): array
    {
        return [
            'id' => $this->id,
            'name' => $this->name,
            'email' => $this->email,
            'created_at' => $this->created_at->toIso8601String(),
            'updated_at' => $this->updated_at->toIso8601String(),
        ];
    }
}

Models

Eloquent models representing database tables.

app/Models/
├── User.php
├── Post.php
├── Comment.php
├── Category.php
├── Tag.php
├── Traits/
│   ├── HasUuid.php
│   ├── Searchable.php
│   ├── HasSlug.php
│   └── SoftDeletesWithTrashed.php
├── Scopes/
│   ├── ActiveScope.php
│   └── PublishedScope.php
└── Observers/
    ├── UserObserver.php
    └── PostObserver.php

Naming Convention:

  • Use singular, capitalized names
  • Match the table name (singular form)

Example:

php
// app/Models/User.php
namespace App\Models;

use Illuminate\Foundation\Auth\User as Authenticatable;
use App\Models\Traits\HasUuid;

class User extends Authenticatable
{
    use HasUuid;

    protected $fillable = [
        'name',
        'email',
        'password',
    ];

    protected $hidden = [
        'password',
        'remember_token',
    ];

    protected $casts = [
        'email_verified_at' => 'datetime',
    ];
}

Services

Business logic layer separating concerns from controllers.

app/Services/
├── Auth/
│   ├── AuthService.php
│   ├── TokenService.php
│   └── PasswordResetService.php
├── User/
│   ├── UserService.php
│   ├── UserProfileService.php
│   └── UserPreferenceService.php
├── Post/
│   └── PostService.php
├── Payment/
│   ├── PaymentService.php
│   └── StripeService.php
└── Notification/
    ├── NotificationService.php
    └── EmailService.php

Example:

php
// app/Services/User/UserService.php
namespace App\Services\User;

use App\Models\User;
use App\Repositories\UserRepository;

class UserService
{
    public function __construct(
        private UserRepository $userRepository
    ) {}

    public function createUser(array $data): User
    {
        // Business logic
        return $this->userRepository->create($data);
    }

    public function updateUser(User $user, array $data): User
    {
        // Business logic
        return $this->userRepository->update($user, $data);
    }
}

Repositories

Data access layer abstracting database operations.

app/Repositories/
├── Contracts/
│   ├── RepositoryInterface.php
│   ├── UserRepositoryInterface.php
│   └── PostRepositoryInterface.php
├── BaseRepository.php
├── UserRepository.php
└── PostRepository.php

Example:

php
// app/Repositories/Contracts/UserRepositoryInterface.php
namespace App\Repositories\Contracts;

use App\Models\User;

interface UserRepositoryInterface
{
    public function findById(int $id): ?User;
    public function create(array $data): User;
    public function update(User $user, array $data): User;
    public function delete(User $user): bool;
}

// app/Repositories/UserRepository.php
namespace App\Repositories;

use App\Models\User;
use App\Repositories\Contracts\UserRepositoryInterface;

class UserRepository implements UserRepositoryInterface
{
    public function findById(int $id): ?User
    {
        return User::find($id);
    }

    public function create(array $data): User
    {
        return User::create($data);
    }

    // ... other methods
}

Events & Listeners

Event-driven architecture components.

app/Events/
├── Auth/
│   ├── UserRegistered.php
│   └── UserLoggedIn.php
└── Post/
    ├── PostCreated.php
    └── PostPublished.php

app/Listeners/
├── Auth/
│   ├── SendWelcomeEmail.php
│   └── LogUserLogin.php
└── Post/
    ├── NotifySubscribers.php
    └── UpdateSearchIndex.php

Jobs

Queueable jobs for asynchronous processing.

app/Jobs/
├── User/
│   ├── ImportUsers.php
│   └── ExportUsers.php
├── Email/
│   ├── SendBulkEmail.php
│   └── SendNewsletter.php
└── Data/
    ├── SyncExternalData.php
    └── GenerateReport.php

Example:

php
// app/Jobs/Email/SendWelcomeEmail.php
namespace App\Jobs\Email;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;

class SendWelcomeEmail implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public function __construct(
        private User $user
    ) {}

    public function handle()
    {
        // Send email
    }
}

Policies

Authorization logic for model operations.

app/Policies/
├── UserPolicy.php
├── PostPolicy.php
└── CommentPolicy.php

Example:

php
// app/Policies/PostPolicy.php
namespace App\Policies;

use App\Models\User;
use App\Models\Post;

class PostPolicy
{
    public function view(User $user, Post $post): bool
    {
        return true;
    }

    public function update(User $user, Post $post): bool
    {
        return $user->id === $post->user_id;
    }

    public function delete(User $user, Post $post): bool
    {
        return $user->id === $post->user_id || $user->isAdmin();
    }
}

Database Structure

database/
├── factories/
│   ├── UserFactory.php
│   └── PostFactory.php
├── migrations/
│   ├── 2024_01_01_000000_create_users_table.php
│   ├── 2024_01_01_000001_create_posts_table.php
│   └── 2024_01_02_000000_add_status_to_posts_table.php
└── seeders/
    ├── DatabaseSeeder.php
    ├── UserSeeder.php
    ├── PostSeeder.php
    └── RoleSeeder.php

Migration Naming

Migrations should follow this pattern:

{timestamp}_{action}_{table_name}_table.php

Examples:
2024_01_01_000000_create_users_table.php
2024_01_02_000000_add_email_to_users_table.php
2024_01_03_000000_add_index_to_users_email.php
2024_01_04_000000_drop_old_column_from_users_table.php

Routes Structure

routes/
├── api.php
├── web.php
├── console.php
└── channels.php

Organization within route files:

php
// routes/api.php

// API V1 Routes
Route::prefix('v1')->group(function () {

    // Public routes
    Route::post('auth/login', [AuthController::class, 'login']);
    Route::post('auth/register', [AuthController::class, 'register']);

    // Authenticated routes
    Route::middleware('auth:sanctum')->group(function () {

        // User routes
        Route::prefix('users')->group(function () {
            Route::get('/', [UserController::class, 'index']);
            Route::post('/', [UserController::class, 'store']);
            Route::get('/{id}', [UserController::class, 'show']);
            Route::put('/{id}', [UserController::class, 'update']);
            Route::delete('/{id}', [UserController::class, 'destroy']);
        });

        // Post routes
        Route::apiResource('posts', PostController::class);
    });
});

// API V2 Routes
Route::prefix('v2')->group(function () {
    // V2 routes
});

Config Structure

config/
├── app.php
├── auth.php
├── cache.php
├── database.php
├── filesystems.php
├── logging.php
├── mail.php
├── queue.php
├── services.php
└── custom/
    ├── payment.php
    ├── api.php
    └── notifications.php

Tests Structure

tests/
├── Feature/
│   ├── Api/
│   │   ├── V1/
│   │   │   ├── Auth/
│   │   │   │   ├── LoginTest.php
│   │   │   │   └── RegisterTest.php
│   │   │   ├── User/
│   │   │   │   ├── CreateUserTest.php
│   │   │   │   ├── UpdateUserTest.php
│   │   │   │   └── DeleteUserTest.php
│   │   │   └── Post/
│   │   │       └── PostCrudTest.php
│   │   └── V2/
│   └── Web/
├── Unit/
│   ├── Services/
│   │   ├── UserServiceTest.php
│   │   └── AuthServiceTest.php
│   ├── Repositories/
│   │   └── UserRepositoryTest.php
│   └── Models/
│       └── UserTest.php
└── TestCase.php

Test Naming Convention:

  • Suffix with Test
  • Match the class being tested
  • Use descriptive method names

Example:

php
// tests/Feature/Api/V1/User/CreateUserTest.php
namespace Tests\Feature\Api\V1\User;

use Tests\TestCase;

class CreateUserTest extends TestCase
{
    public function test_can_create_user_with_valid_data()
    {
        // Test implementation
    }

    public function test_cannot_create_user_with_duplicate_email()
    {
        // Test implementation
    }
}

Storage Structure

storage/
├── app/
│   ├── public/
│   │   ├── avatars/
│   │   ├── documents/
│   │   └── uploads/
│   └── private/
├── framework/
│   ├── cache/
│   ├── sessions/
│   └── views/
└── logs/
    └── laravel.log

Best Practices

1. One Class Per File

Each file should contain only one class:

php
// Good
// app/Services/User/UserService.php
namespace App\Services\User;

class UserService
{
    // Implementation
}

// Bad - Multiple classes in one file

2. Namespace Matches Directory

File location should match namespace:

php
// File: app/Services/User/UserService.php
namespace App\Services\User; // Matches directory structure

3. Consistent Naming

  • Use PascalCase for class names
  • Use singular nouns for models
  • Use descriptive names indicating purpose

Keep related functionality together:

app/Services/User/
├── UserService.php
├── UserProfileService.php
└── UserPreferenceService.php

5. Separate Concerns

Different layers in separate directories:

app/
├── Http/Controllers/    # Request handling
├── Services/           # Business logic
├── Repositories/       # Data access
└── Models/            # Data models

6. Version API Controllers

Keep API versions separated:

app/Http/Controllers/Api/
├── V1/
└── V2/

7. Use Subdirectories

For large modules, create subdirectories:

app/Http/Controllers/Api/V1/
├── Auth/
│   ├── LoginController.php
│   ├── RegisterController.php
│   └── PasswordController.php
└── User/
    ├── UserController.php
    └── UserProfileController.php

File Naming Patterns

TypePatternExample
Controller{Resource}Controller.phpUserController.php
Model{Resource}.phpUser.php
Request{Action}{Resource}Request.phpStoreUserRequest.php
Resource{Resource}Resource.phpUserResource.php
Service{Resource}Service.phpUserService.php
Repository{Resource}Repository.phpUserRepository.php
Event{Resource}{Action}.phpUserCreated.php
Listener{Action}{Resource}.phpSendWelcomeEmail.php
Job{Action}{Resource}.phpProcessUserImport.php
Policy{Resource}Policy.phpUserPolicy.php
Migration{date}_{action}_{table}_table.phpcreate_users_table.php
Test{Class}Test.phpUserControllerTest.php

CPR - Clinical Patient Records