Zum Hauptinhalt springen

Laravel-API-Guide

Worum geht's?

Du kennst die API-Architekturen und willst sie in Laravel sauber umsetzen. Dieser Guide zeigt pro Architektur die Laravel-typischen Bausteine, die empfohlenen Pakete und die wichtigsten Best Practices.

Voraussetzung

Laravel 11+. Die Beispiele nutzen PHP 8.3-Syntax (Constructor Property Promotion, readonly, enums).


1. REST – das Brot-und-Butter-Pattern​

Bausteine​

  • Routes: routes/api.php mit Route::apiResource
  • Controller: Single-Action Controllers (Invokable) oder Resource-Controller
  • FormRequest: Validation
  • API-Resource: Output-Transformation
  • Policy: Authorization

Routen​

// routes/api.php
use App\Http\Controllers\Api\OrderController;

Route::middleware(['auth:sanctum', 'throttle:60,1'])->group(function () {
Route::apiResource('orders', OrderController::class);
Route::post('orders/{order}/cancel', [OrderController::class, 'cancel']);
});

Controller​

// app/Http/Controllers/Api/OrderController.php
final class OrderController extends Controller
{
public function index(IndexOrderRequest $request): AnonymousResourceCollection
{
$this->authorize('viewAny', Order::class);

$orders = Order::query()
->forUser($request->user())
->filter($request->validated())
->latest()
->paginate(perPage: 25);

return OrderResource::collection($orders);
}

public function store(StoreOrderRequest $request): JsonResponse
{
$order = app(CreateOrderAction::class)->execute(
user: $request->user(),
data: $request->validated(),
);

return OrderResource::make($order)
->response()
->setStatusCode(201);
}
}

FormRequest + Resource​

final class StoreOrderRequest extends FormRequest
{
public function rules(): array
{
return [
'items' => ['required', 'array', 'min:1'],
'items.*.product_id' => ['required', 'integer', 'exists:products,id'],
'items.*.quantity' => ['required', 'integer', 'min:1', 'max:100'],
'notes' => ['nullable', 'string', 'max:500'],
];
}
}

final class OrderResource extends JsonResource
{
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'status' => $this->status->value,
'total' => $this->total->format(),
'items' => OrderItemResource::collection($this->whenLoaded('items')),
'created' => $this->created_at->toIso8601String(),
'links' => [
'self' => route('orders.show', $this->resource),
'cancel' => route('orders.cancel', $this->resource),
],
];
}
}

Best Practices​

  • Action-Klassen statt Fat-Controller-Methoden (CreateOrderAction, CancelOrderAction)
  • Konsistente Fehlerformate via App\Exceptions\Handler (RFC 7807 / JSON:API)
  • OpenAPI mit darkaonline/l5-swagger oder Scribe automatisch generieren
  • API-Versionierung ΓΌber URL-Prefix (/api/v1/) oder Accept-Header
  • Pagination nur via paginate() (Cursor fΓΌr unendliche Listen: cursorPaginate())
  • Rate Limiting mit benannten Limitern in RouteServiceProvider
  • Idempotency-Key-Header fΓΌr POSTs prΓΌfen (gegen Doppel-Submits)

Pakete​


2. GraphQL​

Empfohlener Stack: Lighthouse​

composer require nuwave/lighthouse
php artisan lighthouse:install

Schema-First​

# graphql/schema.graphql
type Query {
user(id: ID! @eq): User @find
orders(
status: OrderStatus @eq
first: Int! = 25
page: Int
): [Order!]! @paginate(defaultCount: 25)
}

type Mutation {
createOrder(input: CreateOrderInput! @spread): Order!
@field(resolver: "App\\GraphQL\\Mutations\\CreateOrder")
@guard
}

type Order {
id: ID!
status: OrderStatus!
total: String!
items: [OrderItem!]! @hasMany
user: User! @belongsTo
}

enum OrderStatus {
PENDING
PAID
SHIPPED
CANCELLED
}

input CreateOrderInput {
items: [OrderItemInput!]!
notes: String
}

Resolver​

final class CreateOrder
{
public function __construct(
private readonly CreateOrderAction $action,
) {}

public function __invoke(mixed $_, array $args, GraphQLContext $ctx): Order
{
return $this->action->execute(
user: $ctx->user(),
data: $args,
);
}
}

Best Practices​

  • N+1 vermeiden mit @hasMany, @belongsTo (Lighthouse lΓΆst das via DataLoader) oder eigenem BatchLoader
  • Query-KomplexitΓ€t limitieren: lighthouse.security.max_query_depth und max_query_complexity
  • Persisted Queries im Frontend (Apollo) β†’ schΓΌtzt vor DoS und reduziert Payload
  • Authorization: @guard, @can, eigene FieldMiddleware
  • Subscriptions via Laravel Reverb / Pusher / Redis
  • GraphiQL im Dev-Modus aktivieren, in Production deaktivieren
  • Strict Schema: Niemals Any-Scalar – immer typisieren

Alternative​


3. gRPC – Service-zu-Service​

Empfohlener Stack: Spiral RoadRunner + spiral/php-grpc​

PHP-FPM kann gRPC nicht effizient bedienen (kein HTTP/2-Multiplexing). RoadRunner (Go-basierter App-Server) lΓΆst das.

composer require spiral/roadrunner-grpc spiral/php-grpc

Proto​

// proto/order.proto
syntax = "proto3";
package billing;

service OrderService {
rpc GetOrder (OrderRequest) returns (Order);
rpc StreamOrders (OrderFilter) returns (stream Order);
}

message OrderRequest { int32 id = 1; }
message Order {
int32 id = 1;
string status = 2;
double total = 3;
}

Generierung & Service-Implementierung​

protoc --php_out=app/Grpc --grpc-php_out=app/Grpc proto/order.proto
final class OrderServiceImpl implements OrderServiceInterface
{
public function __construct(
private readonly OrderRepository $orders,
) {}

public function GetOrder(GRPC\ContextInterface $ctx, OrderRequest $in): Order
{
$order = $this->orders->find($in->getId())
?? throw new GRPCException('Order not found', StatusCode::NOT_FOUND);

return (new Order())
->setId($order->id)
->setStatus($order->status->value)
->setTotal($order->total->amount());
}
}

Best Practices​

  • mTLS fΓΌr interne Service-Calls – RoadRunner unterstΓΌtzt es nativ
  • Proto-Files in eigenes Repo (oder packages/proto) – versioniert
  • Backward Compatibility: Felder nie umnummerieren, nur hinzufΓΌgen
  • Deadlines & Cancellation clientseitig immer setzen
  • gRPC fΓΌr intern – nach außen weiterhin REST/GraphQL als Edge

4. WebSocket – Echtzeit mit Laravel Reverb​

Reverb ist Laravels offizieller, eigener WebSocket-Server (ab Laravel 11). Pusher-API-kompatibel, lokal lauffΓ€hig.

composer require laravel/reverb
php artisan reverb:install
php artisan reverb:start

Broadcasting-Event​

final class OrderShipped implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets, SerializesModels;

public function __construct(public readonly Order $order) {}

public function broadcastOn(): array
{
return [new PrivateChannel("orders.{$this->order->id}")];
}

public function broadcastWith(): array
{
return [
'tracking_url' => $this->order->tracking_url,
'shipped_at' => $this->order->shipped_at->toIso8601String(),
];
}
}

Channel-Auth​

// routes/channels.php
Broadcast::channel('orders.{order}', function (User $user, Order $order) {
return $user->id === $order->user_id;
});

Client (Laravel Echo)​

import Echo from 'laravel-echo';
import Pusher from 'pusher-js';

window.Echo = new Echo({
broadcaster: 'reverb',
key: import.meta.env.VITE_REVERB_APP_KEY,
wsHost: import.meta.env.VITE_REVERB_HOST,
wsPort: 8080,
forceTLS: false,
});

Echo.private(`orders.${orderId}`)
.listen('OrderShipped', (e) => updateUI(e));

Best Practices​

  • PrivateChannel / PresenceChannel mit Auth, nie ΓΆffentlich fΓΌr Userdaten
  • Reverb hinter Reverse Proxy (Nginx mit proxy_set_header Upgrade)
  • Skalierung horizontal via Redis-Pub/Sub-Backend
  • Reconnect-Strategien im Echo-Client konfigurieren
  • Whisper (.whisper()) fΓΌr Client-zu-Client-Events (Typing-Indicators, Cursors)
  • Heartbeat ΓΌber pings_interval konfigurieren

Alternativen​

  • Soketi – externer Pusher-kompatibler Server (Node)
  • Pusher Channels (SaaS)
  • Ably (SaaS)

5. Server-Sent Events – LLM-Streaming, Notifications​

Laravel kann SSE ΓΌber StreamedResponse direkt liefern – kein Reverb nΓΆtig.

final class LlmStreamController
{
public function __invoke(StreamRequest $request, ClaudeClient $claude): StreamedResponse
{
return response()->stream(function () use ($request, $claude) {
foreach ($claude->stream($request->validated()) as $chunk) {
echo "data: " . json_encode($chunk) . "\n\n";
ob_flush();
flush();
}
echo "event: done\ndata: {}\n\n";
}, headers: [
'Content-Type' => 'text/event-stream',
'Cache-Control' => 'no-cache',
'X-Accel-Buffering' => 'no', // Nginx-Buffering aus
'Connection' => 'keep-alive',
]);
}
}

Best Practices​

  • PHP-FPM Timeout und max_execution_time hochsetzen (oder via Octane)
  • Nginx: proxy_buffering off fΓΌr SSE-Routen
  • Frontend EventSource mit automatischem Reconnect nutzen
  • Authentifizierung ΓΌber Query-Param oder Cookie – EventSource unterstΓΌtzt keine Custom-Header
  • Heartbeat-Events alle 15s, damit Proxies die Verbindung nicht killen
  • Octane oder FrankenPHP in Production – klassisches PHP-FPM blockiert sonst Worker

6. Webhooks – eingehend​

Ein eingehender Webhook (z. B. Stripe)​

// routes/api.php
Route::post('webhooks/stripe', StripeWebhookController::class)
->withoutMiddleware([VerifyCsrfToken::class]);
final class StripeWebhookController
{
public function __invoke(Request $request): Response
{
try {
$event = Webhook::constructEvent(
$request->getContent(),
$request->header('Stripe-Signature'),
config('services.stripe.webhook_secret'),
);
} catch (SignatureVerificationException) {
abort(401, 'Invalid signature');
}

// Idempotenz: jeden Stripe-Event-Hash 1Γ— behandeln
if (WebhookLog::where('external_id', $event->id)->exists()) {
return response('Already processed', 200);
}

ProcessStripeEvent::dispatch($event->toArray());

WebhookLog::create(['external_id' => $event->id, 'type' => $event->type]);

return response('OK', 200);
}
}

Best Practices​

  • Signatur verifizieren – bei JEDEM Webhook
  • Idempotenz ΓΌber Event-ID-Tracking
  • Sofort 2xx zurΓΌckgeben, schwere Arbeit in Queue-Job verschieben
  • Logging in eigener Tabelle (webhook_logs) fΓΌr Replay/Audit
  • Spatie Webhook Client als fertiges Paket (spatie/laravel-webhook-client)

Ausgehende Webhooks (du verschickst)​

β†’ spatie/laravel-webhook-server – HMAC-Signing, Retries, Backoff, Worker.


7. SOAP​

PHP hat eingebauten SOAP-Support (ext-soap). In Laravel meist als Wrapper im Service.

final class ElsterClient
{
private SoapClient $client;

public function __construct(string $wsdl)
{
$this->client = new SoapClient($wsdl, [
'trace' => true,
'exceptions' => true,
'soap_version' => SOAP_1_2,
'cache_wsdl' => WSDL_CACHE_BOTH,
]);
}

public function submitReport(array $data): SubmitResult
{
try {
$response = $this->client->__soapCall('Submit', [$data]);
return SubmitResult::fromResponse($response);
} catch (SoapFault $e) {
Log::error('SOAP fault', [
'fault' => $e->getMessage(),
'request' => $this->client->__getLastRequest(),
'response' => $this->client->__getLastResponse(),
]);
throw new ElsterUnavailable($e->getMessage(), previous: $e);
}
}
}

Best Practices​

  • WSDL lokal cachen (cache_wsdl => WSDL_CACHE_BOTH)
  • __getLastRequest/Response fΓΌr Debugging einschalten
  • Domain-Exceptions statt rohe SoapFaults nach oben werfen
  • WSSecurity / Signing nur via offizielles Paket (robrichards/wse-php)
  • Tests gegen WSDL-Mock (phpro/soap-client kann das)

8. tRPC-Bridge – Laravel als Backend fΓΌr TS-Frontend​

tRPC ist TS-only. Wenn du tRPC-Style-Type-Safety mit Laravel-Backend willst:

// app/Data/OrderData.php
final class OrderData extends Data
{
public function __construct(
public readonly int $id,
public readonly OrderStatus $status,
public readonly string $total,
/** @var DataCollection<int, OrderItemData> */
public readonly DataCollection $items,
) {}
}

β†’ php artisan typescript:transform produziert eine resources/types/generated.d.ts mit OrderData als TS-Interface. Frontend nutzt es direkt.


9. Queues – asynchrone Workloads​

Laravel's Queue-Layer ist eines seiner stΓ€rksten Features.

Job​

final class ProcessStripeEvent implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

public int $tries = 5;
public int $backoff = 30;
public int $timeout = 120;

public function __construct(public readonly array $event) {}

public function handle(StripeEventHandler $handler): void
{
$handler->process($this->event);
}

public function uniqueId(): string
{
return $this->event['id']; // Idempotenz!
}
}

Best Practices​

  • Treiber-Wahl: Redis (default), SQS fΓΌr Cloud, RabbitMQ fΓΌr komplexe Routings
  • Horizon fΓΌr Redis-Queues – Dashboard, Auto-Scaling, Metriken
  • Backoff exponentiell bei Retry: [10, 30, 60, 120, 300]
  • ShouldBeUnique fΓΌr Idempotenz auf Lock-Ebene
  • Batches fΓΌr Job-Gruppen mit Callback-Logik
  • Job-Middleware fΓΌr Throttling externer APIs (new RateLimited('stripe-api'))
  • Failed-Jobs-Table ΓΌberwachen / Alerts setzen
  • Tests: Queue::fake() und Bus::fake()

10. Event-Streaming mit Kafka / Redpanda​

FΓΌr Event-Sourcing oder Hochlast-Pipelines.

composer require mateusjunges/laravel-kafka
// Producer
Kafka::publishOn('orders.events')
->withHeaders(['event-type' => 'OrderCreated'])
->withBodyKey('order_id', $order->id)
->withBodyKey('total', $order->total->amount())
->send();

// Consumer (in Artisan-Command)
Kafka::consumer(['orders.events'])
->withHandler(function (ConsumedMessage $message) {
// …
})
->build()
->consume();

Best Practices​

  • Schemas mit Avro oder Protobuf, registriert im Schema-Registry
  • Consumer-Groups fΓΌr horizontale Skalierung
  • Idempotenz im Consumer (z. B. Outbox-Pattern fΓΌr Producer)
  • Dead-Letter-Topic fΓΌr unbehandelbare Nachrichten

11. Gesamtarchitektur in Laravel – ein Beispiel​

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Edge / Public β”‚
β”‚ REST API (Sanctum + Scribe-Doku) routes/api.php β”‚
β”‚ GraphQL fΓΌr SPA /graphql via Lighthouse β”‚
β”‚ Webhooks von Stripe/GitHub routes/api.php (signed) β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Realtime β”‚
β”‚ WebSocket (Reverb) fΓΌr Live-UI Broadcasting Events β”‚
β”‚ SSE fΓΌr LLM-Streaming StreamedResponse + Octane β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Service-zu-Service β”‚
β”‚ gRPC (RoadRunner) zu Pricing-Service proto/*.proto β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Async / Background β”‚
β”‚ Redis-Queue + Horizon (Jobs) app/Jobs β”‚
β”‚ Kafka (Event-Stream) Outbox-Pattern β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Legacy β”‚
β”‚ SOAP-Client zu ELSTER app/Services/Elster* β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

12. Cross-Cutting Best Practices in Laravel​

Authentication​

  • SPA-Same-Domain: Sanctum Cookie-Mode
  • Mobile / 3rd Party: Sanctum Personal Access Tokens oder Passport OAuth2
  • Service-zu-Service: mTLS (gRPC) oder kurzlebige JWTs

Validation​

  • FormRequest-Klassen fΓΌr REST/GraphQL-Inputs
  • Spatie Data fΓΌr typisierte DTOs
  • Niemals Request-Daten direkt ins Eloquent-Model ohne Validation
  • Enum-Casts fΓΌr Status-Felder

Authorization​

  • Policies fΓΌr Eloquent-Modelle
  • Gates fΓΌr globale Permissions
  • AuthorizeResource in Controllern, @guard/@can in GraphQL

Fehlerformat (RFC 7807)​

// app/Exceptions/Handler.php
$this->renderable(function (Throwable $e, Request $request) {
if (! $request->expectsJson()) return null;

return match (true) {
$e instanceof ValidationException => response()->json([
'type' => 'https://example.com/errors/validation',
'title' => 'Validation failed',
'status' => 422,
'errors' => $e->errors(),
], 422),
$e instanceof ModelNotFoundException => response()->json([
'type' => 'https://example.com/errors/not-found',
'title' => 'Resource not found',
'status' => 404,
], 404),
default => null,
};
});

Logging & Observability​

  • Structured Logging (JSON-Format in Production)
  • Telescope in Dev / Staging
  • Sentry oder Bugsnag fΓΌr Errors
  • OpenTelemetry ΓΌber open-telemetry/opentelemetry-laravel
  • Per-Request-Trace-ID als Middleware (X-Request-Id)

Testing​

// Pest
it('creates an order', function () {
$user = User::factory()->create();

$response = $this->actingAs($user)->postJson('/api/orders', [
'items' => [['product_id' => 1, 'quantity' => 2]],
]);

$response->assertCreated()
->assertJsonPath('data.status', 'pending');

expect(Order::count())->toBe(1);
});
  • Feature-Tests mit actingAs() fΓΌr REST/GraphQL
  • Event::fake(), Queue::fake(), Bus::fake() fΓΌr Async
  • Http::fake() fΓΌr externe APIs / Webhooks
  • gRPC: separate Tests gegen RoadRunner-Container in CI

Caching​

  • HTTP-Caching via etag()-Header und Cache-Control
  • Eloquent-Cache sparsam – lieber gezielte Cache::remember in Actions
  • Tagged Cache mit Redis fΓΌr invalidierbare Gruppen

Rate Limiting​

// app/Providers/RouteServiceProvider.php
RateLimiter::for('api', fn (Request $r) =>
Limit::perMinute(60)->by($r->user()?->id ?: $r->ip())
);

RateLimiter::for('webhooks', fn (Request $r) =>
Limit::perMinute(300)->by($r->ip())
);

API-Versionierung​

  • URL-Versionierung (/api/v1/...) – am pragmatischsten
  • Header-Versionierung (Accept: application/vnd.example.v2+json) – eleganter, schwerer cachebar
  • Niemals breaking changes ohne neue Version
  • Deprecation-Header (Sunset, Deprecation) im Übergang

13. Pakete-Cheatsheet​

ZweckPaket
REST-Dokuknuckleswtf/scribe, darkaonline/l5-swagger
Query-Builder mit Filternspatie/laravel-query-builder
GraphQLnuwave/lighthouse
gRPCspiral/php-grpc, roadrunner-server/grpc
WebSocketlaravel/reverb, soketi/soketi
Webhooks (in)spatie/laravel-webhook-client
Webhooks (out)spatie/laravel-webhook-server
Data Transfer Objectsspatie/laravel-data
TS-Codegenspatie/typescript-transformer
Inertiainertiajs/inertia-laravel
Queue-Dashboardlaravel/horizon
Kafkamateusjunges/laravel-kafka
Octane (Performance)laravel/octane
FrankenPHPdunglas/frankenphp
OpenAPI-Validationhkulekci/kafka-laravel

14. Sicherheits-Checkliste (Laravel-spezifisch)​

Vor Production prΓΌfen
  • APP_DEBUG=false in Production – immer
  • HTTPS erzwingen via URL::forceScheme('https') oder Trusted-Proxy-Konfig
  • CORS in config/cors.php explizit – keine * fΓΌr Auth-Endpoints
  • Sanctum stateful domains korrekt konfiguriert
  • CSRF fΓΌr Web-Routes aktiv, fΓΌr API-Routes ausgenommen
  • Mass Assignment: $guarded = [] nur mit FormRequest-Validation davor – sonst $fillable whitelisten
  • Eloquent Scope forUser() ΓΌberall, wo Multi-Tenancy gilt
  • SQL-Injection via Query-Builder geschΓΌtzt – nie roh DB::raw() mit User-Input
  • Webhook-Signaturen prΓΌfen (Stripe, GitHub, Mollie)
  • Rate-Limiter auf JEDEM ΓΆffentlichen Endpoint
  • Reverb: PrivateChannel mit Auth, niemals Channel fΓΌr Userdaten
  • gRPC: mTLS zwischen Services

15. WeiterfΓΌhrend​

  • API-Architekturen-Überblick β†’ API-Architekturen
  • Live-OpenAPI-Playground β†’ API Playground
  • Laravel-Coding-Guidelines deines Teams β†’ BSH-Skill bsh-code-guideline

Externe Quellen

Quote

β€žJede API-Architektur hat in Laravel ein gutes Paket – die Kunst liegt nicht in der Implementierung, sondern in der Wahl der richtigen Architektur fΓΌr den richtigen Use-Case."