Building a Digital Product for TeknoClass — A Modular Approach with Laravel 11 — part 1.
Introduction
excited to launch a new series documenting how my colleague Mahmoud and I are building an API-driven digital product from scratch using Laravel 11. This article will focus on our architectural decisions, specifically our implementation of a modular approach that provides scalability, maintainability, and clear separation of concerns.
Why We Chose Modular Architecture
After careful consultation with our management team, we decided to implement a modular architecture for several compelling reasons:
- Scalability: Modular architecture allows us to scale specific components independently as our user base grows without affecting the entire system.
- Clear Separation of Components: Each module represents a distinct business domain with its own controllers, services, and repositories, making the codebase more organized and easier to navigate.
- Enhanced Maintainability: Isolated modules enable our development team to work in parallel without stepping on each other’s toes, reducing merge conflicts and simplifying troubleshooting.
- Future-Proofing: The modular approach makes it significantly easier to integrate new features or replace outdated components as the product evolves over time.
Our Infrastructure Blueprint
Core Module
The Core Module serves as the foundation of our application, hosting fundamental functionality and base classes used across the entire system.
Key Components:
- Base exceptions (such as
AuthenticationException
andGeneralException
) - Base request classes for standardized validation
- Core service providers that bootstrap essential services
- Shared interfaces that define contracts for implementation across modules
This module ensures consistency in how we handle errors, validate input, and register services throughout the application.
Shared Module
The Shared Module contains reusable utilities and functionality that aren’t tied to a specific domain, fostering code reuse and consistency.
Key Components:
- Helper functions for common tasks like file handling and string manipulation
- Data Transfer Objects (DTOs) for structured data exchange
- Utility services that provide cross-cutting functionality
- Custom validation rules used across multiple modules
By centralizing these common components, we reduce redundancy and ensure a consistent developer experience across the codebase.
Access Control Module
The Access Control Module implements our Role-Based Access Control (RBAC) system, managing roles and permissions across the application.
Key Components:
- Role and Permission models with appropriate relationships
- Custom middleware for authorization checks
- Policies and gates for fine-grained access control
- Services for assigning and checking permissions
This modular approach to access control ensures that authorization logic is centralized and consistent, improving both security and maintainability.
Auth Module
The Auth Module manages all authentication processes, including login, registration, and password management.
Key Components:
- Authentication controllers handling login/logout flows
- Registration and password reset functionality
- Passport and OAuth integration for secure authentication
- Security measures like rate limiting and two-factor authentication
By isolating authentication concerns, we can maintain robust security practices while keeping the rest of the codebase focused on business functionality.
User Module
The User Module encapsulates all user-related features, from profile management to account settings.
Structure:
- HTTP Layer: Controllers, Form Requests, and Laravel Resources for standardized API responses
- Services Layer: Business logic and data processing before interacting with the Repository layer
- Repository Layer: Data access using the Eloquent ORM
- Data Transfer: DataTransferObjects to securely move data from Form Requests to the
Each module also includes an EventServiceProvider to facilitate event-based communication, ensuring that different parts of the system can interact seamlessly while maintaining loose coupling.
Implementation Details
Beyond the high-level architecture, we’ve implemented specific components to ensure consistency and maintainability across our codebase.
API Structure and Versioning
We’ve structured our API layer with clear versioning from the start:
- Created a dedicated
Api
folder within each module's HTTP controllers to separate API endpoints from potential web routes - Implemented a subfolder structure with
V1
to support future API versioning without breaking backward compatibility
This approach ensures we can evolve our API over time while maintaining support for existing clients.
Core Controller Hierarchy
We’ve established a robust controller hierarchy to standardize API behavior:
Base Controller
<?php
declare(strict_types=1);
namespace Modules\Core\Http\Controllers;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Validation\ValidatesRequests;
abstract class Controller
{
use AuthorizesRequests, ValidatesRequests;
}
This abstract base controller incorporates Laravel’s authorization and validation traits, providing a foundation for all controllers in the system.
Base API Controller
<?php
declare(strict_types=1);
namespace Modules\Core\Http\Controllers\Api;
use Illuminate\Http\JsonResponse;
use Modules\Core\{
Helpers\ApiResponse,
Constants\HttpStatusConstants,
Http\Controllers\Controller
};
class BaseApiController extends Controller
{
protected function successResponse(
string $message = 'SUCCESS',
int $statusCode = HttpStatusConstants::HTTP_200_OK,
mixed $data = null,
): JsonResponse {
return ApiResponse::success(
message: $message,
statusCode: $statusCode,
data: $data,
);
}
protected function errorResponse(
string $message = 'ERROR',
int $statusCode = HttpStatusConstants::HTTP_400_BAD_REQUEST,
mixed $data = null,
): JsonResponse {
return ApiResponse::error(
message: $message,
statusCode: $statusCode,
data: $data,
);
}
}
The BaseApiController
extends our core controller and introduces standardized methods for returning consistent API responses.
Versioned API Controller
<?php
declare(strict_types=1);
namespace Modules\Core\Http\Controllers\Api\V1;
use Modules\Core\Http\Controllers\Api\BaseApiController;
abstract class BaseApiV1Controller extends BaseApiController {}
This versioned controller allows us to introduce version-specific behavior in the future while maintaining a clean inheritance chain.
Standardized API Responses
To ensure consistent API responses across all endpoints, we’ve implemented a dedicated ApiResponse
helper:
ApiResponse
<?php
declare(strict_types=1);
namespace Modules\Core\Helpers;
use Illuminate\Http\JsonResponse;
use Modules\Core\Constants\HttpStatusConstants;
final class ApiResponse
{
public static function success(string $message = 'SUCCESS', int $statusCode = HttpStatusConstants::HTTP_200_OK, mixed $data = null): JsonResponse
{
return response()->json([
'success' => true,
'message' => $message,
'data' => $data,
'status_code' => $statusCode,
'timestamp' => now()->toIso8601String(),
], $statusCode);
}
public static function error(string $message = 'ERROR', int $statusCode = HttpStatusConstants::HTTP_400_BAD_REQUEST, mixed $data = null): JsonResponse
{
return response()->json([
'success' => false,
'message' => $message,
'errors' => $data,
'status_code' => $statusCode,
'timestamp' => now()->toIso8601String(),
], $statusCode);
}
}
This class ensures that all API responses follow the same structure, including success status, message, data/errors, status code, and timestamp.
Form Request Validation
We’ve implemented a robust validation hierarchy to standardize request validation across all endpoints:
Base Form Request
<?php
declare(strict_types=1);
namespace Modules\Core\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
abstract class BaseFormRequest extends FormRequest
{
final public function authorize(): bool
{
return true;
}
}
This abstract class extends Laravel’s FormRequest
and provides a default authorization implementation.
Base API Form Request
<?php
declare(strict_types=1);
namespace Modules\Core\Http\Requests\Api;
use Override;
use Modules\Core\{
Helpers\ApiResponse,
Constants\HttpStatusConstants,
Http\Requests\BaseFormRequest
};
use Illuminate\Contracts\Validation\Validator;
use Illuminate\Http\Exceptions\HttpResponseException;
abstract class BaseApiFormRequest extends BaseFormRequest
{
#[Override]
protected function failedValidation(Validator $validator): void
{
$jsonResponse = ApiResponse::error(
message: 'VALIDATION_FAILED',
statusCode: HttpStatusConstants::HTTP_422_UNPROCESSABLE_ENTITY,
data: $validator->errors(),
);
throw new HttpResponseException($jsonResponse);
}
}
The BaseApiFormRequest
overrides the failedValidation
method to ensure validation errors are returned in our standardized API response format with the appropriate HTTP status code.
Implementation Steps
To implement our modular architecture, we took several key steps:
- Package Integration: After thorough research, we integrated the nWidart Laravel Modules package following the official documentation. This decision has laid a solid foundation for our project structure.
- Restructuring Core Components: We transferred default Laravel structure files into their appropriate modules, with base controllers, requests, and helpers moved to the Core Module to ensure consistent behavior across the application.
- Module-Specific Database Components: We moved migrations, seeders, and factories into their respective modules (using the
newFactory
method) to streamline development and maintain clear boundaries between modules. - Configuration Adjustments: We updated various configuration settings, such as changing the User Model’s path from
App/Models/User.php
toModules/User/Models/User.php
, to align with our modular setup.
Folder Structure
Our modular approach follows this organized structure:
modules/
├── Core/
│ ├── Constants/
│ │ └── HttpStatusConstants.php
│ ├── Exceptions/
│ ├── Helpers/
│ │ └── ApiResponse.php
│ ├── Http/
│ │ ├── Controllers/
│ │ │ ├── Controller.php
│ │ │ └── Api/
│ │ │ ├── BaseApiController.php
│ │ │ └── V1/
│ │ │ └── BaseApiV1Controller.php
│ │ └── Requests/
│ │ ├── BaseFormRequest.php
│ │ └── Api/
│ │ └── BaseApiFormRequest.php
│ └── Providers/
├── Shared/
│ ├── DTOs/
│ ├── Helpers/
│ └── Services/
├── AccessControl/
│ ├── Models/
│ ├── Http/
│ │ ├── Controllers/
│ │ │ └── Api/
│ │ │ └── V1/
│ │ ├── Middleware/
│ │ └── Requests/
│ │ └── Api/
│ │ └── V1/
│ └── Services/
├── Auth/
│ ├── Http/
│ │ ├── Controllers/
│ │ │ └── Api/
│ │ │ └── V1/
│ │ └── Requests/
│ │ └── Api/
│ │ └── V1/
│ └── Services/
└── User/
├── Models/
│ └── User.php
├── Http/
│ ├── Controllers/
│ │ └── Api/
│ │ └── V1/
│ ├── Requests/
│ │ └── Api/
│ │ └── V1/
│ └── Resources/
├── Services/
└── Repositories/
This structure provides clear organization while maintaining Laravel’s familiar conventions within each module.
Benefits We’re Already Seeing
Even in the early stages of development, our modular approach is delivering tangible benefits:
- Improved Development Velocity: Team members can work on different modules simultaneously without conflicts.
- Enhanced Code Quality: The clear separation of concerns forces us to think carefully about dependencies and interfaces.
- Easier Onboarding: New team members can focus on understanding one module at a time rather than grasping the entire system at once.
- Simplified Testing: Modules can be tested in isolation, making it easier to write comprehensive unit and integration tests.
- Consistent API Responses: Our standardized response structure ensures that all endpoints behave predictably, improving the developer experience for API consumers
What’s Next
Stay tuned for the next post in this series, where we’ll dive deeper into:
- The project’s new structure in detail
- Bootstrap initialization process
- Implementation of rate limiting for API endpoints
- Authentication flow with Laravel Passport
- Swagger integration for API documentation
I welcome any participation, review, and constructive suggestions as we build in public. The code will soon be available on GitHub, and you can follow the series on Telegram, Medium, and LinkedIn.
Conclusion
Adopting a modular architecture with Laravel 11 has set us on a path toward building a scalable, maintainable digital product for TeknoClass. By establishing clear boundaries between components and focusing on separation of concerns, we’re creating a codebase that can evolve and grow with the business needs.
The implementation of standardized controllers, requests, and response formatting provides a consistent foundation that will pay dividends as the project grows. Our careful attention to versioning from the outset ensures we can evolve the API without breaking changes for existing clients.
Whether you’re building a new application or considering a refactor of an existing system, I hope our approach provides valuable insights into implementing modular architecture with Laravel.
#DigitalProduct #Development #BuildingInPublic #Programming #Laravel #ModularArchitecture #CleanCode #SoftwareEngineering