This commit represents the complete foundational codebase for Nimbus Alpha, a Laravel package that provides an integrated, in-browser API client with automatic schema discovery from validation rules. IMPORTANT: This is a squashed commit representing the culmination of extensive development, refactoring, and architectural iterations. All previous commit history has been intentionally removed to provide a clean foundation for the public alpha release. The development of Nimbus involved: - Multiple architectural refactorings - Significant structural changes - Experimental approaches that were later abandoned - Learning iterations on the core concept - Migration between different design patterns This messy history would: - Make git blame confusing and unhelpful - Obscure the actual intent behind current implementation - Create noise when reviewing changes - Reference deleted or refactored code If git blame brought you to this commit, it means you're looking at code that was part of the initial alpha release. Here's what to do: 1. Check Current Documentation - See `/wiki/contribution-guide/README.md` for architecture details - Review the specific module's README if available - Look for inline comments explaining the reasoning 2. Look for Related Code - Check other files in the same module - Look for tests that demonstrate intended behavior - Review interfaces and contracts 3. Context Matters - This code may have been updated since alpha - Check git log for subsequent changes to this file - Look for related issues or PRs on GitHub --- This commit marks the beginning of Nimbus's public journey. All future commits will build upon this foundation with clear, traceable history. Thank you for using or contributing to Nimbus!
32 KiB
Contributor Guide
Welcome to the Nimbus contributor guide. This document provides everything you need to understand Nimbus's architecture and contribute effectively.
Table of Contents
Understanding Nimbus
Getting Started
Contributing
Reference
Architecture Overview
Nimbus follows a modular architecture that separates concerns into distinct, focused modules. The package automatically extracts validation schemas from Laravel routes and provides an interface for testing them.
High-Level Architecture
%%{init: {'theme':'base', 'themeVariables': { 'primaryColor': '#ffffff', 'primaryTextColor': '#18181b', 'primaryBorderColor': '#d4d4d8', 'lineColor': '#71717a', 'secondaryColor': '#f4f4f5', 'tertiaryColor': '#e4e4e7'}}}%%
graph TB
subgraph "Laravel Application"
LaravelRoutes[Routes] --> LaravelControllers[Controllers]
LaravelControllers -.-> ValidationRules[Inline Validation Rules]
LaravelControllers -.-> FormRequests[Form Requests]
end
subgraph "Schema Initialization"
RouteExtractor[Rules Extractor] -->|"Passes Normalized Rules"| SchemaBuilder[Schema Builder]
SchemaBuilder ----->|"produces"| JSONSchema[JSON Schemas]
end
RouteExtractor -.->|"Inspects"| ValidationRules
RouteExtractor -.->|"Inspects"| FormRequests
RouteExtractor -.->|"reads"| LaravelRoutes
subgraph "HTTP Client"
VueFrontend[Vue Frontend \n <small>HTTP Client</small>]
VueFrontend -->|Makes Request| RequestRelay[Request Relay]
RequestRelay --> |"Builds"| HTTPResponses["Relay Response"] -->VueFrontend
end
JSONSchema --> VueFrontend
style LaravelRoutes fill:#ffffff,stroke:#71717a,stroke-width:2px,color:#18181b
style RouteExtractor fill:#f4f4f5,stroke:#71717a,stroke-width:2px,color:#18181b
style SchemaBuilder fill:#e4e4e7,stroke:#71717a,stroke-width:2px,color:#18181b
style VueFrontend fill:#d4d4d8,stroke:#71717a,stroke-width:2px,color:#18181b
style RequestRelay fill:#a1a1aa,stroke:#71717a,stroke-width:2px,color:#ffffff
Module Structure
Backend Modules
src/
├── Commands/ # Artisan commands
├── Http/ # Controllers and middleware
├── Intellisenses/ # TypeScript interface generation
└── Modules/ # Core business logic
├── Config/ # Configuration enums and constants
├── Routes/ # Route extraction and normalization
├── Schemas/ # Schema generation and transformation
└── Relay/ # HTTP request relay and authorization
Module responsibilities:
- Config - Application configuration, enums, and constants.
- Routes - Extract and normalize Laravel routes and validation rules.
- Schemas - Transform validation rules into JSON schemas.
- Relay - Forward HTTP requests and handle authentication.
- Intellisenses - Generate TypeScript interfaces for backend entities.
Frontend Structure
resources/js/
├── app/ # Application setup and initialization
├── components/ # Vue components
│ ├── base/ # Reusable UI components (buttons, inputs)
│ ├── common/ # Shared components (modals, dropdowns)
│ ├── domain/ # Business logic components (request builder)
│ └── layout/ # Layout components (panels, navigation)
├── composables/ # Vue composables (reactive logic)
├── stores/ # Pinia stores (global state)
├── utils/ # Utility functions
└── interfaces/ # TypeScript interfaces
Core Concepts
Route Extraction Process
The route extraction process analyzes Laravel routes and extracts normalized validation schemas.
%%{init: {'theme':'base', 'themeVariables': { 'primaryColor': '#ffffff', 'primaryTextColor': '#18181b', 'primaryBorderColor': '#d4d4d8', 'lineColor': '#71717a', 'secondaryColor': '#f4f4f5', 'tertiaryColor': '#e4e4e7'}}}%%
flowchart TD
LaravelRoutes[Laravel Routes] -->|"filter by prefix"| RouteFiltering[Route Filtering]
RouteFiltering --> TransformRoute[Normalize Route into `ExtractableRoute` object]
subgraph SchemaLoop["Loop over ExtractableRoute objects"]
TransformRoute --> SchemaExtraction[Schema Extraction]
SchemaExtraction -->|"find matching strategy"| StrategySelection[Strategy Selection]
StrategySelection -->|"generate JSON schema"| SchemaGeneration[Schema Generation]
SchemaGeneration --> JSONSchemaOutput[ExtractedRoute object Output]
end
RouteFiltering -..-> Fork1@{shape: "fork"}
Fork1 --> PrefixFilter[Apply Prefix Filter]
Fork1 --> IgnoredRoutesSub[Exclude Ignored Routes]
style LaravelRoutes fill:#ffffff,stroke:#71717a,stroke-width:2px,color:#18181b
style JSONSchemaOutput fill:#f4f4f5,stroke:#71717a,stroke-width:2px,color:#18181b
style SchemaExtraction fill:#e4e4e7,stroke:#71717a,stroke-width:2px,color:#18181b
style SchemaLoop fill:#f9fafb,stroke:#d4d4d8,stroke-dasharray: 4 2
Key steps:
- Route Filtering - Filter routes by configured prefix, exclude ignored routes.
- Normalization - Transform Laravel routes into
ExtractableRouteobjects. - Strategy Selection - Choose appropriate extraction strategy (Form Request vs inline validation).
- Schema Generation - Convert validation rules to JSON schema.
- Output - Produce
ExtractedRouteobjects with complete metadata.
Schema Building Process
The schema builder converts Laravel validation rules into JSON Schema format with support for nested structures.
%%{init: {'theme':'base', 'themeVariables': { 'primaryColor': '#ffffff', 'primaryTextColor': '#18181b', 'primaryBorderColor': '#d4d4d8', 'lineColor': '#71717a', 'secondaryColor': '#f4f4f5', 'tertiaryColor': '#e4e4e7'}}}%%
flowchart TD
ValidationRules[Validation Rules] --> SortedRules[Sort by processing order]
SortedRules --> FieldType
subgraph FieldLoop["Loop over sorted fields"]
FieldType{Field is of type:}
FieldType --> |Root Field| RootProperty[Build Root Property]
FieldType --> |Dot Notation Field| NestedStructure[Nested Object/Array]
FieldType --> |Array of Primitives| ArrayProperty[Build Array Items Schema]
NestedStructure -->|"recurse over segments"| SegmentType
SegmentType{Segment Type}
SegmentType -->|Other| IntermediateObjects["Add Object Property <br /><small>(if Missing)</small>"]
SegmentType -->|`*` segment| ArrayItems[Array Item Creation]
SegmentType -->|leaf segment| LeafProperty[Build Leaf Property]
end
FieldLoop --> FinalSchema[Output Final Schema]
style ValidationRules fill:#ffffff,stroke:#71717a,stroke-width:2px,color:#18181b
style FinalSchema fill:#f4f4f5,stroke:#71717a,stroke-width:2px,color:#18181b
style NestedStructure fill:#e4e4e7,stroke:#71717a,stroke-width:2px,color:#18181b
style ArrayProperty fill:#e4e4e7,stroke:#71717a,stroke-width:2px,color:#18181b
style RootProperty fill:#e4e4e7,stroke:#71717a,stroke-width:2px,color:#18181b
Key concepts:
- Root fields - Top-level properties (
name,email). - Dot notation - Nested structures (
user.profile.name). - Array wildcards - Array items (
items.*.name). - Property building - Convert validation rules to JSON Schema properties.
- Recursive processing - Handle deeply nested structures.
Request Flow
How user interactions translate to API calls through the relay system.
%%{init: {'theme':'base', 'themeVariables': { 'primaryColor': '#ffffff', 'primaryTextColor': '#18181b', 'primaryBorderColor': '#d4d4d8', 'lineColor': '#71717a', 'secondaryColor': '#f4f4f5', 'tertiaryColor': '#e4e4e7'}}}%%
sequenceDiagram
participant User
participant VueFrontend as Vue Frontend
participant LaravelBackend as Laravel Backend
participant APIEndpoint as API Endpoint
critical Initialization
LaravelBackend->>VueFrontend: Populate Routes with Schemas
end
User->>VueFrontend: Select Route
VueFrontend->>User: Display Route on UI
User->>VueFrontend: Configure Request
User->>VueFrontend: Trigger Request
VueFrontend->>VueFrontend: Prepare request for relaying
VueFrontend->>LaravelBackend: Send Request to Relay Endpoint
LaravelBackend->>APIEndpoint: Relay HTTP Request
APIEndpoint->>LaravelBackend: Return Response
LaravelBackend->>LaravelBackend: Prepare endpoint response into relay response
LaravelBackend->>VueFrontend: Return relayed response
VueFrontend->>User: Display Response
Design Principles
Single Responsibility Principle
Each class has one clear purpose and reason to change.
Examples:
RouteExtractorService- Orchestrates route extraction.SchemaBuilder- Converts validation rules to schemas.RequestRelayAction- Handles HTTP request forwarding.PropertyBuilder- Builds individual schema properties.
Dependency Injection
Services are injected rather than instantiated directly.
class RouteExtractorService
{
public function __construct(
protected SchemaExtractor $schemaExtractor,
protected ExtractableRouteFactory $routeFactory,
protected IgnoredRoutesService $ignoredRoutesService,
) {}
}
Immutable Value Objects
Data structures don't change after creation, ensuring predictable behavior.
readonly class ExtractedRoute
{
public function __construct(
public readonly Endpoint $uri,
public readonly array $methods,
public readonly Schema $schema,
) {}
}
Strategy Pattern
Different algorithms encapsulated in separate classes for flexibility.
Examples:
FormRequestExtractorStrategy- Extracts rules from Form Request classes.InlineRequestValidatorExtractorStrategy- Extracts inline validation.BearerAuthorizationHandler- Handles Bearer token auth.CurrentUserAuthorizationHandler- Handles session-based auth.
Actions vs Services
The codebase distinguishes between Actions and Services based on their responsibilities.
Actions
Definition: A single, composable unit of work that performs one discrete operation.
Characteristics:
- Single responsibility - performs one complete operation.
- Composable - can be called by other actions, services, or controllers.
- Stateless - only holds dependencies, no internal mutable state.
- Returns results - produces DTOs, value objects, or collections.
- Workflow-oriented - may orchestrate multiple steps internally.
Example:
class RequestRelayAction
{
public function execute(RelayRequest $request): RelayResponse
{
// Performs one complete workflow: relay HTTP request
}
}
Services
Definition: A reusable helper that encapsulates domain-specific logic or manages data.
Characteristics:
- Reusable logic - provides methods used by multiple actions or controllers.
- May hold state - if part of its responsibility.
- Non-orchestrating - it does not perform complete workflows.
- Domain/infrastructure focused - manages persistence, caching, configuration.
Example:
class IgnoredRoutesService
{
public function isIgnored(string $routeName): bool
{
// Provides reusable logic for route filtering
}
}
Quick Comparison
| Feature | Action | Service |
|---|---|---|
| Purpose | Single unit of work / workflow | Reusable domain or infrastructure logic |
| State | Stateless beyond dependencies | Can hold state if necessary |
| Return | Value, DTO, collection | Self, Side-effect, or data |
| Orchestration | Can orchestrate multiple steps | Does not orchestrate |
| Composable | Yes | Usually used by actions or controllers |
Rule of Thumb: If the class performs one discrete operation and returns a result, it's an Action. If the class provides reusable logic or manages state, it's a Service.
Development Environment Setup
Local Development Setup
1. Clone the Package Repository
git clone https://github.com/sunchayn/nimbus.git
cd nimbus
composer install
npm install
2. Clone the Development Repository
The dev repository provides a foundation for testing the package with a Laravel application.
git clone https://github.com/sunchayn/nimbus-dev.git
cd nimbus-dev
composer run setup
3. Start a Web Server
Important: The relay endpoint requires a real web server. Do not use php artisan serve.
Options:
- Laravel Herd - Recommended for macOS.
- Laravel Sail - Docker-based environment.
- Valet - Nginx-based for macOS.
- Docker/Nginx/Apache - Manual setup.
Why not the built-in server?
PHP's built-in server is single-threaded. When the relay endpoint makes a request back to the application, it creates a deadlock where the server waits for itself to respond.
4. Run Frontend Development Server
# Inside nimbus directory
npm run dev
5. Access Nimbus
Navigate to your Laravel application's Nimbus endpoint:
http://your-app.test/nimbus
Testing Different Laravel Versions
Use Composer scripts to switch between Laravel versions:
# Switch to Laravel 10
composer run switch:l10
# Switch to Laravel 11
composer run switch:l11
# Switch to Laravel 12
composer run switch:l12
These scripts update orchestra/testbench and all related dependencies automatically.
After switching, run tests to verify compatibility:
composer test:parallel
Development Workflow
Code Organization
Follow the established module structure when adding new functionality.
Backend modules:
src/Modules/
├── Config/ # Add new configuration enums here
├── Routes/ # Route extraction and normalization logic
├── Schemas/ # Schema generation and transformation
└── Relay/ # HTTP relay and authorization handlers
Frontend structure:
resources/js/
├── components/
│ ├── base/ # Reusable UI components (buttons, inputs)
│ ├── common/ # Shared components (modals, notifications)
│ ├── domain/ # Business logic components (request builder)
│ └── layout/ # Layout components (panels, sidebar)
├── composables/ # Reactive stateful logic
├── stores/ # Global state management (Pinia)
└── utils/ # Pure utility functions
Git Workflow
1. Fork and Clone
git clone https://github.com/YOUR_USERNAME/nimbus.git
cd nimbus
git remote add upstream https://github.com/sunchayn/nimbus.git
2. Create Feature Branch
git checkout -b feature/your-feature-name origin/base
Use descriptive branch names:
feature/add-openapi-supportfix/schema-generation-nested-arraysrefactor/extract-property-builder
3. Make Changes
- Follow coding standards (see Coding Standards).
- Write tests for new functionality.
- Update documentation if needed.
- Keep commits focused and atomic.
4. Test Your Changes
# Backend tests
composer test
# Frontend tests
npm run test:run
# Linting
composer style:fix
npm run style:fix
5. Commit Changes
Use conventional commit messages:
git add .
git commit -m "feat(schemas): add support for nested array validation"
Commit format: type(scope): description
Types:
feat- New feature.fix- Bug fix.refactor- Code restructuring.docs- Documentation changes.test- Adding tests.chore- Maintenance tasks.build- Dependencies or CI changes.
5. Push and Create Pull Request
git push -u origin feature/your-feature-name
Then create a pull request on GitHub with:
- Clear description of changes.
- Reference to related issues.
- Screenshots for UI changes.
- Testing instructions.
Testing Strategy
Backend Testing
Test organization:
- Tests mirror the module structure.
- Each service has corresponding test classes.
- Integration tests verify end-to-end functionality.
- Unit tests focus on individual methods.
Testing principles:
- Test behavior, not implementation.
- Mock external dependencies.
- Use descriptive test names.
- Keep tests focused and independent.
Running tests:
# Run all tests
composer test
# Run tests in parallel (faster)
composer test:parallel
# Run with coverage
composer test:coverage
# Run specific test class
composer test -- --filter SchemaBuilderTest
# Run specific test method
composer test -- --filter testBuildsSchemaFromValidationRules
Test example:
class SchemaBuilderUnitTest extends TestCase
{
public function test_it_builds_schema_for_simple_validation_rules(): void
{
// Arrange
$rules = [
'name' => ['required', 'string', 'max:255'],
'email' => ['required', 'email'],
'age' => ['integer', 'min:18'],
];
// Act
$schema = $this->schemaBuilder->build($rules);
// Assert
$this->assertArrayHasKey('name', $schema['properties']);
$this->assertEquals('string', $schema['properties']['name']['type']);
$this->assertTrue($schema['properties']['name']['required']);
}
}
Frontend Testing
Component testing guidelines:
- Test user interactions and outcomes.
- Mock external dependencies (API calls, stores).
- Use
data-testidattributes for selectors. - Focus on component behavior, not implementation details.
Linting and formatting:
# Check styling issues
npm run style:check
# Automatically fix styling issues
npm run style:fix
Running component tests:
# Run all tests
npm run test:run
# Run tests in watch mode
npm run test
# Run specific test file
npm run test:run -- RequestBuilder.test.ts
Test example:
import { mount } from '@vue/test-utils'
import { describe, it, expect } from 'vitest'
import RequestBuilder from '@/components/domain/RequestBuilder.vue'
describe('RequestBuilder', () => {
it('validates required fields', async () => {
const wrapper = mount(RequestBuilder, {
props: {
schema: {
properties: {
name: { type: 'string', required: true }
}
}
}
})
// Trigger validation
await wrapper.find('[data-testid="submit-btn"]').trigger('click')
// Assert error message appears
expect(wrapper.find('[data-testid="error-message"]').text())
.toContain('name is required')
})
})
Contributing Process
Before You Start
- Check existing issues - Someone may already be working on it.
- Open an issue first - For major changes, discuss the approach.
- Read the documentation - Understand the architecture and patterns.
- Set up your environment - Follow the setup guide above.
Development Process
Follow the Git Workflow guide.
Update Documentation
If your changes affect:
- User-facing features → Update User Guide
- Architecture or patterns → Update Contributor Guide
- Configuration → Update config comments and README
Submit Pull Request
PR title format: type(scope): brief description
PR description should include:
- What changes were made.
- Why the changes were necessary.
- How to test the changes.
- Screenshots for UI changes.
- Related issues (e.g., "Closes #123").
Example PR description:
## Description
Adds support for extracting validation rules from custom validation rule objects.
## Motivation
Currently, Nimbus only supports array-based validation rules. Many Laravel apps use custom rule objects, which are not extracted.
## Changes
- Added `RuleObjectExtractor` to handle custom rule objects
- Updated `SchemaExtractor` to detect and use the new extractor
- Added tests for rule object extraction
## Testing
1. Create a route with custom rule object validation
2. Access Nimbus interface
3. Verify the route appears with correct schema
Closes #42
Pull Request Guidelines
Code Review Checklist
Before submitting, verify:
- Code follows established patterns and conventions.
- All tests pass.
- New functionality has test coverage.
- Documentation is updated.
- No linting or formatting errors.
- Commits are clean and well-described.
- PR description is clear and complete.
What Reviewers Look For
Code quality:
- Follows SOLID principles.
- Appropriate use of Actions vs Services.
- Proper dependency injection.
- Clear, descriptive naming.
Testing:
- Adequate test coverage.
- Tests focus on behavior, not implementation.
- Edge cases are considered.
- Tests are independent and repeatable.
Documentation:
- Code is self-documenting where possible.
- Complex logic has explanatory comments.
- User-facing changes documented in User Guide.
- Architecture changes documented in Contributor Guide.
Performance:
- No obvious performance issues.
- Efficient algorithms and data structures.
- Appropriate use of caching.
Breaking changes:
- Clearly documented.
- Migration path provided if applicable.
- Version bump follows semantic versioning.
Coding Standards
PHP Coding Standards
Method Naming
Use action-oriented, domain-specific names that clearly communicate intent.
Good examples:
// Action-oriented + domain-specific
public function switchToRouteDefinitionOf(string $method): void
public function initializeRequest(Route $route, array $availableRoutes): void
public function executeCurrentRequest(): void
public function validateUserPreferences(array $data): bool
public function generateCurlCommand(Request $request): string
Poor examples:
// Implementation-focused, unclear intent
public function updateSchemaAndPayloadForMethod(string $method): void
public function setupRequestData(Route $route, array $routes): void
public function processRequest(): void
public function doValidation(array $data): bool
Type Declarations
Always use type declarations for parameters and return types.
// Good
public function buildSchema(array $rules): array
{
// Implementation
}
// Bad
public function buildSchema($rules)
{
// Implementation
}
Readonly Properties
Use readonly properties for immutable data.
readonly class ExtractedRoute
{
public function __construct(
public readonly Endpoint $uri,
public readonly array $methods,
public readonly Schema $schema,
) {}
}
Vue.js Coding Standards
Composition API
Always use <script setup> with TypeScript.
<script setup lang="ts">
import { ref, computed } from 'vue'
// Props
const props = defineProps<{
title: string
items: Item[]
}>()
// Emits
const emit = defineEmits<{
update: [value: string]
}>()
// State
const isLoading = ref(false)
// Computed
const filteredItems = computed(() => {
return props.items.filter(item => item.active)
})
// Methods
const handleUpdate = (value: string) => {
emit('update', value)
}
</script>
<template>
<!-- Template content -->
</template>
Component Structure Order
Organize component code in this order:
- Imports.
- Props and emits.
- Reactive state (ref, reactive).
- Computed properties.
- Methods/functions.
- Lifecycle hooks.
- Watch statements.
TypeScript Usage
- Use strict type checking.
- Define interfaces for complex objects.
- Avoid
anytype. - Use type assertions sparingly.
// Good - explicit types
interface RequestConfig {
method: string
url: string
headers: Record<string, string>
body?: unknown
}
const config: RequestConfig = {
method: 'POST',
url: '/api/users',
headers: { 'Content-Type': 'application/json' }
}
// Bad - any type
const config: any = { /* ... */ }
Style Guides
Comment Style Guide
Docblock Format
Use docblocks to explain purpose and business context, not implementation.
/**
* Builds a JSON Schema from Laravel validation rules.
*
* This method handles nested structures using dot notation and array wildcards.
* It processes fields in a specific order to ensure parent objects exist before
* child properties are added.
*
* @param array<string, array<int, string|object>> $rules Laravel validation rules
* @return array<string, mixed> JSON Schema representation
*/
public function build(array $rules): array
{
// Implementation
}
Inline Comments
Explain why decisions were made, not what the code does.
// Good - explains reasoning
// Process parent fields first to ensure nested objects exist
$sortedFields = $this->sortFieldsByDepth($fields);
// Bad - restates the obvious
// Loop through the fields
foreach ($fields as $field) {
// ...
}
TODO Comments
Use categorized TODO comments for future work.
// TODO [Feature]: Add support for custom validation rule objects
// TODO [Enhancement]: Cache compiled schemas for better performance
// TODO [Bug]: Fix handling of conditional validation rules
// TODO [Refactor]: Extract cookie decryption into dedicated service
// TODO [Performance]: Optimize schema generation for large rule sets
// TODO [Documentation]: Add examples for complex nested structures
// TODO [Test]: Add integration tests for authentication flow
Allowed categories:
| Category | Purpose | Example |
|---|---|---|
| Feature | New functionality planned | Investigate supporting Closures for route extraction |
| Enhancement | Improvement to existing functionality | Convert logged-in user to bearer token |
| Bug | Known defect that needs fixing | Implement impersonation logic properly |
| Refactor | Code reorganization needed | Extract cookie creation into service |
| Performance | Optimization opportunity | Cache authorization handlers |
| Documentation | Missing or incomplete docs | Add examples for custom auth strategies |
| Test | Missing or incomplete test coverage | Add integration tests for relay flow |
Tailwind CSS Usage
Use Utility Classes Only
Use Tailwind's utility classes exclusively. Avoid custom CSS when possible.
<!-- Good - Tailwind utilities -->
<div class="flex h-screen max-h-screen overflow-hidden">
<div class="bg-white border-r border-zinc-200 p-4">
<h2 class="text-lg font-semibold text-zinc-900 mb-4">Routes</h2>
</div>
</div>
<!-- Bad - custom CSS -->
<div class="custom-layout">
<div class="sidebar">
<h2 class="sidebar-title">Routes</h2>
</div>
</div>
<style>
.custom-layout { /* ... */ }
.sidebar { /* ... */ }
</style>
Component Class Extraction
For repeated patterns, extract to component props or composables, not CSS classes.
<script setup lang="ts">
const buttonClasses = computed(() => ({
base: 'px-4 py-2 rounded font-medium transition-colors',
primary: 'bg-blue-600 hover:bg-blue-700 text-white',
secondary: 'bg-gray-200 hover:bg-gray-300 text-gray-900'
}))
</script>
Architecture Rules
Backend Architecture Rules
Controllers
Controllers orchestrate, they don't contain business logic.
// Good - controller delegates to action
class RelayController extends Controller
{
public function __construct(
private RequestRelayAction $relayAction
) {}
public function relay(Request $request): JsonResponse
{
$relayRequest = RelayRequest::fromRequest($request);
$relayResponse = $this->relayAction->execute($relayRequest);
return response()->json($relayResponse);
}
}
// Bad - controller contains business logic
class RelayController extends Controller
{
public function relay(Request $request): JsonResponse
{
// Complex logic here
$client = new Client();
$response = $client->request(/* ... */);
// More logic...
return response()->json($data);
}
}
Services
Services handle single business concerns and use dependency injection.
class SchemaExtractorService
{
public function __construct(
private FormRequestExtractorStrategy $formRequestStrategy,
private InlineRequestValidatorExtractorStrategy $inlineStrategy,
) {}
public function extract(ExtractableRoute $route): ?array
{
// Single responsibility: extract validation rules
// Delegates to appropriate strategy
}
}
Value Objects and DTOs
Use immutable value objects and DTOs for data transfer and manipulation.
// Immutable value object
readonly class RelayRequest
{
public function __construct(
public string $method,
public string $url,
public array $headers,
public ?array $body,
public ?AuthorizationType $authorization,
) {}
public static function fromRequest(Request $request): self
{
return new self(
method: $request->input('method'),
url: $request->input('url'),
headers: $request->input('headers', []),
body: $request->input('body'),
authorization: AuthorizationType::tryFrom($request->input('authorization.type')),
);
}
}
Frontend Architecture Rules
Component Organization
Group related code together and keep components focused.
<script setup lang="ts">
// 1. Imports
import { ref, computed, onMounted } from 'vue'
import { useRequestStore } from '@/stores/request'
// 2. Props and emits
const props = defineProps<{
route: Route
}>()
const emit = defineEmits<{
submit: [request: RequestConfig]
}>()
// 3. Stores and composables
const requestStore = useRequestStore()
const { validatePayload } = useValidation()
// 4. Reactive state
const payload = ref<Record<string, unknown>>({})
const errors = ref<ValidationError[]>([])
// 5. Computed properties
const isValid = computed(() => errors.value.length === 0)
// 6. Methods
const handleSubmit = () => {
errors.value = validatePayload(payload.value)
if (isValid.value) {
emit('submit', { ...payload.value })
}
}
// 7. Lifecycle hooks
onMounted(() => {
// Initialize
})
</script>
State Management
Use appropriate state management strategies:
- Local state - Component-specific data.
- Composables - Reusable reactive logic.
- Pinia stores - Global application state.
// Composable for reusable reactive logic
export function useValidation(schema: Schema) {
const errors = ref<ValidationError[]>([])
const validate = (data: unknown): boolean => {
errors.value = performValidation(data, schema)
return errors.value.length === 0
}
return {
errors: readonly(errors),
validate
}
}
// Store for global state
export const useRequestStore = defineStore('request', () => {
const currentRequest = ref<RequestConfig | null>(null)
const history = ref<RequestConfig[]>([])
const setCurrentRequest = (request: RequestConfig) => {
currentRequest.value = request
history.value.push(request)
}
return {
currentRequest,
history,
setCurrentRequest
}
})
Final Notes
Continuous Improvement
The codebase has undergone multiple refactoring rounds. Some inconsistencies or confusing parts may exist and will be addressed as the project matures.
Getting Help
- Architecture questions - Open a discussion on GitHub.
- Bug reports - Open an issue with reproduction steps.
- Feature proposals - Start a discussion first to align on approach.