# Blueprint for Enterprise API Design: Architectural Patterns & Implementations

This guide acts as a production-grade **Architectural Blueprint** based on the elite software patterns implemented in the **RamionMarketplace** system. By decoupling business logic, automating repetitive queries, securing file uploads, and standardizing exception rendering, this structure ensures high security, scalability, and extreme developer productivity.

You can use this blueprint to recreate this exact premium architecture in other languages and frameworks (such as **Node.js (Express/TypeScript/NestJS)**, **Go (Gin/Fiber)**, or **Python (FastAPI)**).

---

## Table of Contents
1. [System 1: Global Request/Response & Exception Handling System](#system-1-global-requestresponse--exception-handling-system)
2. [System 2: Sessionless JWT Authentication via HTTP-Only Secure Cookies](#system-2-sessionless-jwt-authentication-via-http-only-secure-cookies)
3. [System 3: Secure File Upload & Private Media Streaming System](#system-3-secure-file-upload--private-media-streaming-system)
4. [System 4: Highly Reusable DRY Repository CRUD Pipeline](#system-4-highly-reusable-dry-repository-crud-pipeline)

---

## System 1: Global Request/Response & Exception Handling System

### 1. The Core Pattern
In a production API, a client should **never** receive direct raw framework exceptions, HTML error pages, or database stack traces. 
- **Response Uniformity**: Every response must follow a strict JSON contract.
- **Exception Interception Pipeline**: Business-logic and framework exceptions are thrown declaratively anywhere in the system and intercepted by a single, centralized Global Exception Handler.

```
Request ──> [Route / Middleware] ──> [Controller] ──> Throws Exception 
                                                           │
                                                           ▼
[Client] <── Standard JSON Response <── [Central ApiException Handler]
```

---

### 2. Implementation in RamionMarketplace

#### A. Central Success & Error response structure (`Controller.php`)
Every API controller extends a base `Controller` containing uniform JSON builders.
```php
abstract class Controller
{
    use HandlesCustomExceptions;

    protected function successResponse(string $message, mixed $data = null, ?int $count = null, int $status = 200): JsonResponse
    {
        $response = [
            'success' => true,
            'message' => $message,
            'data' => $data,
            'statusCode' => $status,
        ];
        if ($count !== null) { $response['count'] = $count; }
        return response()->json($response, $status);
    }

    protected function errorResponse(string $message, mixed $error = null, int $status = 400): JsonResponse
    {
        return response()->json([
            'success' => false,
            'message' => $message,
            'error' => $error,
            'statusCode' => $status,
        ], $status);
    }
}
```

#### B. Declarative Throwing Trait (`HandlesCustomExceptions.php`)
Allows triggering specific API responses anywhere in the application by simply throwing a `CustomApiException`.
```php
trait HandlesCustomExceptions
{
    protected function businessError(string $message): never {
        throw new CustomApiException($message, 400);
    }
    protected function notFound(string $message): never {
        throw new CustomApiException($message, 404);
    }
    protected function accessDenied(string $message): never {
        throw new CustomApiException($message, 403);
    }
    protected function unauthorized(string $message): never {
        throw new CustomApiException($message, 401);
    }
}
```

#### C. central Exception Handler (`ApiExceptionHandler.php`)
This matches framework-native, validation, database, and custom exceptions to consistent JSON schemas.
```php
class ApiExceptionHandler
{
    use HasLogging;

    public function handle(Request $request, Throwable $exception): JsonResponse
    {
        if (!$exception instanceof ValidationException) {
            $this->logException($exception, [
                'url' => $request->fullUrl(),
                'method' => $request->method(),
                'userId' => Auth::id(),
            ]);
        }

        return match (true) {
            $exception instanceof CustomApiException => $this->simpleResponse($exception->getMessage(), $exception->getStatusCode()),
            $exception instanceof AuthenticationException => $this->simpleResponse('Authentication required.', 401),
            $exception instanceof ValidationException => $this->handleValidationException($exception),
            $exception instanceof ModelNotFoundException => $this->handleModelNotFoundException($exception),
            $exception instanceof QueryException => $this->handleQueryException($exception),
            default => $this->handleGenericException($exception),
        };
    }

    private function handleValidationException(ValidationException $exception): JsonResponse
    {
        return $this->errorResponse('The given data was invalid.', $exception->errors(), 422);
    }
}
```
*Wiring up in modern Laravel (`bootstrap/app.php`):*
```php
$exceptions->render(function (Throwable $e, Request $request) {
    return (new \App\Exceptions\ApiExceptionHandler())->handle($request, $e);
});
```

---

### 3. Applying in Other Ecosystems

#### Node.js (Express & TypeScript)
1. **Define a standard Response Interface and Custom Error class**:
```typescript
export class AppError extends Error {
  constructor(public message: string, public statusCode: number) {
    super(message);
    Error.captureStackTrace(this, this.constructor);
  }
}
```
2. **Central Error Middleware**:
```typescript
import { Request, Response, NextFunction } from 'express';

export const globalErrorHandler = (
  err: Error | AppError,
  req: Request,
  res: Response,
  next: NextFunction
) => {
  const statusCode = err instanceof AppError ? err.statusCode : 500;
  const message = err.message || 'Internal Server Error';

  res.status(statusCode).json({
    success: false,
    message,
    statusCode,
    error: process.env.NODE_ENV === 'development' ? err.stack : null,
  });
};
```

#### Python (FastAPI)
1. **Define standard Pydantic response models and Exception class**:
```python
from fastapi import HTTPException

class AppError(HTTPException):
    def __init__(self, message: str, status_code: int = 400):
        super().__init__(status_code=status_code, detail=message)
```
2. **Global Exception Handlers**:
```python
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
from fastapi.exceptions import RequestValidationError

app = FastAPI()

@app.exception_handler(AppError)
async def app_error_handler(request: Request, exc: AppError):
    return JSONResponse(
        status_code=exc.status_code,
        content={
            "success": False,
            "message": exc.detail,
            "statusCode": exc.status_code
        }
    )

@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request: Request, exc: RequestValidationError):
    return JSONResponse(
        status_code=422,
        content={
            "success": False,
            "message": "Validation failed",
            "error": exc.errors(),
            "statusCode": 422
        }
    )
```

---

## System 2: Sessionless JWT Authentication via HTTP-Only Secure Cookies

### 1. The Core Pattern
Modern applications use stateless JWTs. However, storing tokens in browser `localStorage` leaves them highly vulnerable to **XSS (Cross-Site Scripting)** attacks.
- **Secure Strategy**: Store the JWT inside a **Secure, HTTP-Only, SameSite Cookie** issued by the server. JavaScript cannot access this cookie, making theft via malicious scripts impossible. 
- **Seamless Frontend DX**: The browser automatically attaches this cookie on all requests going to the API domain.

```
[Frontend Client]                               [API Server]
   │                                                 │
   ├─── 1. POST /login ─────────────────────────────>│ (Verify user)
   │<── 2. Response JSON + Set-Cookie (HTTP-Only) ───┤ (Generates JWT)
   │                                                 │
   ├─── 3. GET /profile (Cookie attached auto) ─────>│ (Validate JWT from Cookie)
   │<── 4. Success JSON ─────────────────────────────┤
```

---

### 2. Implementation in RamionMarketplace

#### A. Central Auth Guard Checking Cookies (`JwtGuard.php`)
This guard implements Laravel's `Guard` interface. It prioritizes checking the Cookie, but falls back to Bearer Header or Query parameters (highly compatible).
```php
protected function getTokenFromRequest(): ?string
{
    // 1. Primary method: Check HTTP-only cookie
    $cookieToken = $this->request->cookie('accessToken');
    if ($cookieToken) { return $cookieToken; }

    // 2. Fallback: Check standard Bearer Header
    $header = $this->request->header('Authorization');
    if ($header) { return $this->jwtService->extractTokenFromHeader($header); }

    // 3. Fallback: Query parameter
    return $this->request->query('token');
}
```

#### B. Generating and Issuing Cookies (`AuthController.php`)
```php
public function login(LoginRequest $request): JsonResponse
{
    $credentials = $request->validated();
    $user = $this->userRepository->findByEmail($credentials['email']);

    if (!$user || !Hash::check($credentials['password'], $user->password)) {
        $this->unauthorized('Invalid email or password');
    }

    $token = $user->createToken(); // Generates JWT

    $response = $this->successResponse('Login successful', new UserResource($user));

    // Send JWT back inside a secure, Http-only Cookie
    return $response->cookie(
        'accessToken',
        $token,
        config('jwt.ttl') * 60, // Lifetime in seconds
        '/',
        config('session.domain') ?: null,
        config('app.env') !== 'local', // Secure flag (True only in HTTPS prod)
        true, // HttpOnly (Invisible to Javascript)
        false,
        'lax' // SameSite restriction
    );
}

public function logout(): JsonResponse
{
    $response = $this->successResponse('Logged out successfully');
    
    // Clear cookie by setting expiration in the past
    return $response->cookie('accessToken', '', -1, '/');
}
```

#### C. Route protection & permissions middleware (`CheckPermission.php`)
```php
class CheckPermission
{
    public function handle(Request $request, Closure $next, string ...$permissions): Response
    {
        if (!Auth::check()) {
            return response()->json(['success' => false, 'message' => 'Authentication required', 'statusCode' => 401], 401);
        }

        $user = Auth::user();

        // Support multiple permissions check
        if (!empty($permissions) && !$user->hasAnyPermission($permissions)) {
            return response()->json([
                'success' => false,
                'message' => 'Insufficient permissions',
                'statusCode' => 403
            ], 403);
        }

        return $next($request);
    }
}
```

---

### 3. Applying in Other Ecosystems

#### Node.js (Express & TypeScript)
1. Use `cookie-parser` to parse incoming cookies and issue secure JWT cookies:
```typescript
import express, { Request, Response } from 'express';
import jwt from 'jsonwebtoken';
import cookieParser from 'cookie-parser';

const app = express();
app.use(cookieParser());

app.post('/api/auth/login', (req, res) => {
  // 1. Verify User Credentials ...
  const token = jwt.sign({ userId: 'user_123' }, process.env.JWT_SECRET!, { expiresIn: '1d' });

  // 2. Set HttpOnly Cookie
  res.cookie('accessToken', token, {
    httpOnly: true,
    secure: process.env.NODE_ENV === 'production',
    sameSite: 'lax',
    maxAge: 24 * 60 * 60 * 1000 // 1 day
  });

  return res.json({ success: true, message: 'Logged in successfully' });
});
```
2. Authentication Guard Middleware:
```typescript
export const authGuard = (req: Request, res: Response, next: any) => {
  const token = req.cookies.accessToken || req.headers.authorization?.split(' ')[1];

  if (!token) {
    return res.status(401).json({ success: false, message: 'Unauthorized' });
  }

  try {
    const payload = jwt.verify(token, process.env.JWT_SECRET!);
    req.user = payload; // Attach resolved user object to request
    next();
  } catch (err) {
    return res.status(401).json({ success: false, message: 'Invalid token' });
  }
};
```

---

## System 3: Secure File Upload & Private Media Streaming System

### 1. The Core Pattern
Allowing direct client upload mapping to static storage causes security risks. Files need a strong isolation structure:
- **Dynamic Partitioning**: Group uploads by date (`/media/{year}/{month}/`) to prevent directory bottlenecks (OS slow-downs when one folder contains millions of files).
- **Randomized Naming**: Rename original files to completely avoid path-traversal attacks and collisions.
- **Private Stream Endpoint**: For sensitive files (like PDF invoices or documents), **never** serve them as static files. Instead, save them as private and create an API stream route that verifies user permissions before flushing the binary content inline.

---

### 2. Implementation in RamionMarketplace

#### A. Database Schema Model (`Media.php`)
Stores detailed metadata for query filters and format helpers.
```php
class Media extends Model
{
    use HasCustomUuid;

    protected $table = 'media';
    protected $uuidPrefix = 'MED_';

    protected $fillable = [
        'fileName',
        'originalName',
        'mimeType',
        'fileSize',
        'filePath',
        'type',      // image, video, document, archive, other
        'isPublic',  // Secure privacy flag
        'uploadedBy',
    ];

    public function getFullUrlAttribute(): string {
        return asset('storage/' . $this->filePath);
    }
}
```

#### B. Multi-File Upload & Unique Partition logic (`MediaController.php`)
```php
public function upload(UploadMediaRequest $request): JsonResponse
{
    $validated = $request->validated();
    $files = $request->file('files') ?? [$request->file('file')];

    $uploadedMedia = [];
    $directory = "media/" . date('Y') . "/" . date('m'); // Path partitioning

    foreach ($files as $file) {
        $originalName = $file->getClientOriginalName();
        $extension = $file->getClientOriginalExtension();
        $fileNameWithoutExt = pathinfo($originalName, PATHINFO_FILENAME);
        
        // Secure, randomized unique naming strategy
        $fileName = $fileNameWithoutExt . '_' . time() . '_' . uniqid() . '.' . $extension;

        // Store file in server disk
        $filePath = $file->storeAs($directory, $fileName, 'public');

        $media = $this->mediaRepository->create([
            'fileName' => $fileName,
            'originalName' => $originalName,
            'mimeType' => $file->getMimeType(),
            'fileSize' => $file->getSize(),
            'filePath' => $filePath,
            'type' => $validated['type'] ?? $this->determineFileType($file->getMimeType()),
            'isPublic' => $validated['isPublic'] ?? true,
            'uploadedBy' => Auth::id(),
        ]);
        
        $uploadedMedia[] = new MediaResource($media);
    }

    return $this->successResponse('Files uploaded successfully', $uploadedMedia, count($uploadedMedia), 201);
}
```

#### C. Secure Private Media Streaming Endpoint
Files marked as private (`isPublic = false`) bypass public visibility. The streaming handler verifies authorization manually before using inline responses.
```php
public function stream($mediaId)
{
    $media = $this->mediaRepository->find($mediaId);
    if (!$media) { $this->notFound('Media not found'); }

    // 1. Verify access permissions for private files
    if (!$media->isPublic) {
        if (!Auth::check() || (Auth::id() !== $media->uploadedBy && !Auth::user()->hasRole('admin'))) {
            $this->accessDenied('You do not have permission to view this media');
        }
    }

    // 2. Ensure physical file exists
    if (!Storage::disk('public')->exists($media->filePath)) {
        $this->notFound('File not found on server');
    }

    $filePath = Storage::disk('public')->path($media->filePath);

    // 3. Stream binary directly to the browser
    return response()->file($filePath, [
        'Content-Type' => $media->mimeType,
        'Content-Disposition' => 'inline; filename="' . $media->originalName . '"',
    ]);
}
```

---

### 3. Applying in Other Ecosystems

#### Node.js (Express & TypeScript with Multer)
1. **Handling Upload**:
```typescript
import multer from 'multer';
import path from 'path';
import fs from 'fs';

const storage = multer.diskStorage({
  destination: (req, file, cb) => {
    const dir = `uploads/media/${new Date().getFullYear()}/${new Date().getMonth() + 1}`;
    fs.mkdirSync(dir, { recursive: true });
    cb(null, dir);
  },
  filename: (req, file, cb) => {
    const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1e9);
    cb(null, `${path.parse(file.originalname).name}_${uniqueSuffix}${path.extname(file.originalname)}`);
  }
});
const upload = multer({ storage });
```
2. **Streaming Authorized Private File**:
```typescript
app.get('/api/media/stream/:id', authGuard, async (req: Request, res: Response) => {
  const media = await db.media.findUnique({ where: { id: req.params.id } });
  
  if (!media) return res.status(404).json({ success: false, message: 'Media not found' });

  // Auth enforcement
  if (!media.isPublic && media.uploadedBy !== req.user.id && req.user.role !== 'admin') {
    return res.status(403).json({ success: false, message: 'Forbidden' });
  }

  const absolutePath = path.resolve(media.filePath);
  res.setHeader('Content-Type', media.mimeType);
  res.setHeader('Content-Disposition', `inline; filename="${media.originalName}"`);
  
  // Pipeline stream
  fs.createReadStream(absolutePath).pipe(res);
});
```

---

## System 4: Highly Reusable DRY Repository CRUD Pipeline

### 1. The Core Pattern
Standard REST CRUD is repetitive. Writing manual filter, search, sort, and pagination checks in every controller endpoint clogs the codebase.
- **The Pattern**: Decouple database transactions from HTTP endpoints using the **Repository Pattern**.
- **Dynamic Query Pipeline (Query Pipeline Trait)**: Models define declarations (`searchable`, `filterable`, `sortable`). The Base Repository interceptor captures request query strings dynamically and constructs Eloquent/ORM query queries behind the scenes.

```
Controller (Paginated List Call)
      │
      ▼
Repository (Call paginated() wrapper)
      │
      ├─> Applies scopeSearch()     (from request('search') and $searchable)
      ├─> Applies scopeFilter()     (from request($field) and $filterable)
      ├─> Applies scopeSort()       (from request('sort') & request('order'))
      └─> Applies scopePaginate()   (from request('page') & request('count'))
```

---

### 2. Implementation in RamionMarketplace

#### A. The Query Pipeline Trait Scopes
These scopes read global HTTP request parameters automatically without needing manually passed variables.

*`HasSearch.php`*
```php
trait HasSearch {
    public function scopeSearch($query) {
        $searchKey = request('search');
        if ($searchKey && property_exists($this, 'searchable') && is_array($this->searchable)) {
            $query->where(function ($q) use ($searchKey) {
                foreach ($this->searchable as $field) {
                    $q->orWhere($field, 'like', "%{$searchKey}%");
                }
            });
        }
        return $query;
    }
}
```

*`HasFilter.php`*
```php
trait HasFilter {
    public function scopeFilter($query) {
        if (property_exists($this, 'filterable') && is_array($this->filterable)) {
            foreach ($this->filterable as $field) {
                if (request()->has($field)) {
                    $query->where($field, request($field));
                }
            }
        }
        return $query;
    }
}
```

*`HasSort.php` & `HasPagination.php`*
```php
trait HasSort {
    public function scopeSort($query) {
        $sortField = request('sort');
        $order = request('order', 'asc');
        if ($sortField && property_exists($this, 'sortable') && is_array($this->sortable) && in_array($sortField, $this->sortable)) {
            $query->orderBy($sortField, $order);
        }
        return $query;
    }
}

trait HasPagination {
    public function scopeCustomPaginate($query) {
        if (!request()->has('page')) { return $query; }
        $page = request('page', 1);
        $count = request('count', 10);
        return $query->skip(($page - 1) * $count)->take($count);
    }
}
```

#### B. Central Abstract Repository (`BaseRepository.php`)
Processes the entire query pipeline cleanly in one location.
```php
abstract class BaseRepository
{
    protected Model $model;

    public function __construct(Model $model) {
        $this->model = $model;
    }

    public function paginated(array $relations = []): Collection
    {
        return $this->model->query()
            ->with($relations)
            ->search()   // Pipeline 1
            ->filter()   // Pipeline 2
            ->sort()     // Pipeline 3
            ->customPaginate() // Pipeline 4
            ->get();
    }

    public function count(): int {
        return $this->model->query()->search()->filter()->count();
    }
    
    public function find(string $id, array $relations = []): ?Model {
        return $this->model->with($relations)->find($id);
    }
    public function create(array $data): Model { return $this->model->create($data); }
    public function update(string $id, array $data): ?Model {
        $record = $this->find($id);
        if (!$record) return null;
        $record->update($data);
        return $record->fresh();
    }
    public function delete(string $id): bool {
        $record = $this->find($id);
        return $record ? $record->delete() : false;
    }
}
```

#### C. Step-by-Step Clean CRUD Module Example: `Category`

1. **The Model (`Category.php`)**:
   Declares pipeline targets.
```php
class Category extends Model
{
    use HasSearch, HasFilter, HasSort, HasPagination, HasCustomUuid;

    protected $table = 'categories';
    protected $uuidPrefix = 'CAT_';

    // Pipeline Declarations
    protected $searchable = ['name', 'slug'];
    protected $filterable = ['isActive', 'imageId'];
    protected $sortable = ['name', 'createdAt'];

    protected $fillable = ['name', 'slug', 'imageId', 'isActive'];
}
```

2. **The Repository (`CategoryRepository.php`)**:
   Needs **zero** database logic! It gets automatic CRUD + Pipelines by extending `BaseRepository`.
```php
class CategoryRepository extends BaseRepository
{
    public function __construct(Category $model) {
        parent::__construct($model);
    }
}
```

3. **The Controller (`CategoryController.php`)**:
   Decoupled from DB structure. Simple routing calls to standard repository wrappers.
```php
class CategoryController extends Controller
{
    protected CategoryRepository $repository;

    public function __construct(CategoryRepository $repository) {
        $this->repository = $repository;
    }

    public function list(): JsonResponse {
        $categories = $this->repository->paginated(['image']);
        $total = $this->repository->count();
        return $this->successResponse('Categories retrieved successfully', CategoryResource::collection($categories), $total);
    }

    public function create(StoreCategoryRequest $request): JsonResponse {
        $category = $this->repository->create($request->validated());
        return $this->successResponse('Category created successfully', new CategoryResource($category), null, 201);
    }

    public function single($id): JsonResponse {
        $category = $this->repository->find($id, ['image']);
        if (!$category) $this->notFound('Category not found');
        return $this->successResponse('Category retrieved successfully', new CategoryResource($category));
    }
}
```

---

### 3. Applying in Other Ecosystems

#### Node.js (Prisma with Express/NestJS)
You can build a dynamic builder function that parses incoming Express query parameters into a standard Prisma option shape.
```typescript
export function buildPrismaQuery(req: any, searchableFields: string[], filterableFields: string[]) {
  const where: any = {};
  const { search, sort, order, page, count } = req.query;

  // 1. Search pipeline
  if (search && searchableFields.length > 0) {
    where.OR = searchableFields.map(field => ({
      [field]: { contains: search, mode: 'insensitive' }
    }));
  }

  // 2. Filter pipeline
  filterableFields.forEach(field => {
    if (req.query[field] !== undefined) {
      where[field] = req.query[field] === 'true' ? true : req.query[field] === 'false' ? false : req.query[field];
    }
  });

  // 3. Sorting pipeline
  const orderBy = sort ? { [sort]: order === 'desc' ? 'desc' : 'asc' } : undefined;

  // 4. Pagination pipeline
  const take = count ? parseInt(count) : undefined;
  const skip = page && count ? (parseInt(page) - 1) * take! : undefined;

  return { where, orderBy, take, skip };
}
```
*How it's used inside a Node.js Controller:*
```typescript
export const getCategories = async (req: Request, res: Response) => {
  const queryOptions = buildPrismaQuery(req, ['name', 'slug'], ['isActive']);
  
  const [categories, total] = await Promise.all([
    db.category.findMany(queryOptions),
    db.category.count({ where: queryOptions.where })
  ]);

  return res.json({
    success: true,
    message: 'Categories retrieved successfully',
    data: categories,
    count: total
  });
};
```

---

## Architectural Guidelines Summary

Regardless of which framework or programming language you build your next API with, adhering to this pattern contract ensures:
1. **Always Return Standard JSON Structures** (Uniform status codes, error details arrays, structured data scopes).
2. **Never expose static URLs for sensitive resources** (Run auth verification checkpoints on custom file pipelines).
3. **Keep controllers completely "lean" and repository-focused** (Do not run raw DB operations in the controller level).
4. **Encrypt Token Storage Out-Of-Reach from Scripts** (Write state authorization to secure HttpOnly browser parameters).
