Shopware 6 API Endpoint erstellen – Idempotent & sicher
Wenn du mit Shopware 6 arbeitest, kommst du früher oder später an den Punkt, wo die Standard-API nicht mehr ausreicht. Vielleicht brauchst du einen speziellen Endpoint für eine externe Integration, einen B2B-Workflow oder eine komplexe Business-Logik. In diesem Tutorial zeige ich dir, wie du einen Shopware 6 API Endpoint erstellst, der nicht nur funktioniert, sondern auch idempotent und sicher ist.
Warum Custom API Endpoints in Shopware 6?
Die Shopware 6 Store API und Admin API decken viele Use Cases ab. Aber manchmal brauchst du mehr Kontrolle:
- Komplexe Geschäftslogik die mehrere Entities betrifft
- Externe Integrationen mit spezifischen Anforderungen
- Performance-Optimierungen durch gezielte Queries
- Spezielle Validierungen die über Standard-Constraints hinausgehen
Ein selbst erstellter Shopware 6 API Endpoint gibt dir diese Kontrolle.
Die Grundstruktur eines Custom Endpoints
Shopware 6 nutzt das Symfony Routing-System. Ein API Endpoint besteht aus drei Komponenten:
- Route Definition (Annotation oder XML)
- Controller mit der Business-Logik
- Request/Response Handling
Hier ist ein einfaches Beispiel für einen Custom Endpoint:
<?php declare(strict_types=1);
namespace CustomPlugin\Core\Api;
use Shopware\Core\Framework\Context;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
#[Route(defaults: ['_routeScope' => ['api']])]
class CustomApiController extends AbstractController
{
#[Route(
path: '/api/_action/custom/process-order',
name: 'api.action.custom.process.order',
methods: ['POST']
)]
public function processOrder(Request $request, Context $context): JsonResponse
{
$data = json_decode($request->getContent(), true);
// Business Logic hier
return new JsonResponse([
'success' => true,
'data' => ['orderId' => $data['orderId']]
]);
}
}
Die _routeScope mit Wert api sorgt dafür, dass der Endpoint unter /api erreichbar ist und die Standard-API-Authentifizierung nutzt.
Authentifizierung implementieren
Ein sicherer Shopware 6 API Endpoint braucht Authentifizierung. Shopware bietet zwei Hauptmethoden:
Integration Authentication (empfohlen)
Für Server-to-Server Kommunikation nutzt du Integration Keys:
#[Route(defaults: ['_routeScope' => ['api'], 'auth_required' => true])]
class CustomApiController extends AbstractController
{
#[Route(
path: '/api/_action/custom/sync-data',
name: 'api.action.custom.sync.data',
methods: ['POST']
)]
public function syncData(Request $request, Context $context): JsonResponse
{
// Context enthält bereits authentifizierte Integration
$integrationId = $context->getSource()->getIntegrationId();
// Zusätzliche Prüfung möglich
if (!$integrationId) {
return new JsonResponse(['error' => 'Unauthorized'], 401);
}
// Sichere Business Logic
return new JsonResponse(['success' => true]);
}
}
Custom Authentication
Für spezialisierte Use Cases kannst du eigene Authentication implementieren:
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;
private function validateApiKey(Request $request): void
{
$apiKey = $request->headers->get('X-Api-Key');
if (!$apiKey || !$this->isValidApiKey($apiKey)) {
throw new UnauthorizedHttpException('API Key', 'Invalid API Key');
}
}
#[Route(path: '/api/_action/custom/webhook', methods: ['POST'])]
public function webhook(Request $request, Context $context): JsonResponse
{
$this->validateApiKey($request);
// Geschützte Logic
return new JsonResponse(['received' => true]);
}
Idempotenz implementieren
Idempotenz bedeutet: Mehrfache identische Requests haben denselben Effekt wie ein einzelner Request. Das ist kritisch für APIs, die über unzuverlässige Netzwerke laufen.
Idempotency Keys nutzen
use Shopware\Core\Framework\DataAbstractionLayer\EntityRepository;
use Shopware\Core\Framework\Uuid\Uuid;
class IdempotentApiController extends AbstractController
{
public function __construct(
private readonly EntityRepository $customLogRepository
) {}
#[Route(path: '/api/_action/custom/create-resource', methods: ['POST'])]
public function createResource(Request $request, Context $context): JsonResponse
{
$idempotencyKey = $request->headers->get('Idempotency-Key');
if (!$idempotencyKey) {
return new JsonResponse(['error' => 'Idempotency-Key required'], 400);
}
// Prüfen ob Request bereits verarbeitet wurde
$existingResult = $this->checkIdempotencyKey($idempotencyKey, $context);
if ($existingResult) {
return new JsonResponse($existingResult, 200);
}
// Neue Resource erstellen
$resourceId = Uuid::randomHex();
$result = [
'success' => true,
'resourceId' => $resourceId
];
// Ergebnis mit Idempotency Key speichern
$this->storeIdempotencyResult($idempotencyKey, $result, $context);
return new JsonResponse($result, 201);
}
private function checkIdempotencyKey(string $key, Context $context): ?array
{
$criteria = new \Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria();
$criteria->addFilter(new \Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter('idempotencyKey', $key));
$result = $this->customLogRepository->search($criteria, $context)->first();
return $result ? json_decode($result->get('result'), true) : null;
}
}
Error Handling Best Practices
Ein professioneller Shopware 6 API Endpoint liefert konsistente, hilfreiche Fehlermeldungen:
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
#[Route(path: '/api/_action/custom/validate-data', methods: ['POST'])]
public function validateData(Request $request, Context $context): JsonResponse
{
try {
$data = json_decode($request->getContent(), true);
if (json_last_error() !== JSON_ERROR_NONE) {
throw new BadRequestHttpException('Invalid JSON payload');
}
// Validierung
$errors = $this->validatePayload($data);
if (!empty($errors)) {
return new JsonResponse([
'errors' => $errors
], Response::HTTP_UNPROCESSABLE_ENTITY);
}
// Erfolgreiche Verarbeitung
return new JsonResponse(['success' => true]);
} catch (BadRequestHttpException $e) {
return new JsonResponse([
'error' => $e->getMessage()
], Response::HTTP_BAD_REQUEST);
} catch (\Exception $e) {
// Logging nicht vergessen
return new JsonResponse([
'error' => 'Internal server error'
], Response::HTTP_INTERNAL_SERVER_ERROR);
}
}
private function validatePayload(array $data): array
{
$errors = [];
if (!isset($data['email']) || !filter_var($data['email'], FILTER_VALIDATE_EMAIL)) {
$errors[] = 'Invalid email address';
}
if (!isset($data['amount']) || $data['amount'] <= 0) {
$errors[] = 'Amount must be greater than zero';
}
return $errors;
}
Rate Limiting hinzufügen
Schütze deinen Shopware 6 API Endpoint vor Missbrauch:
use Symfony\Component\RateLimiter\RateLimiterFactory;
class RateLimitedApiController extends AbstractController
{
public function __construct(
private readonly RateLimiterFactory $apiLimiter
) {}
#[Route(path: '/api/_action/custom/public-endpoint', methods: ['POST'])]
public function publicEndpoint(Request $request, Context $context): JsonResponse
{
$limiter = $this->apiLimiter->create($request->getClientIp());
if (!$limiter->consume(1)->isAccepted()) {
return new JsonResponse([
'error' => 'Rate limit exceeded'
], 429);
}
// Endpoint Logic
return new JsonResponse(['success' => true]);
}
}
Die Rate Limiter Configuration kommt in services.xml:
<service id="api_rate_limiter" class="Symfony\Component\RateLimiter\RateLimiterFactory">
<argument type="collection">
<argument key="policy">token_bucket</argument>
<argument key="limit">100</argument>
<argument key="rate">
<argument key="interval">1 minute</argument>
</argument>
</argument>
</service>
Testing deines API Endpoints
Teste deinen Shopware 6 API Endpoint gründlich:
# Mit Integration Token
curl -X POST https://your-shop.com/api/_action/custom/process-order \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_INTEGRATION_TOKEN" \
-H "Idempotency-Key: unique-request-id-123" \
-d '{"orderId": "01234567890abcdef"}'
# Idempotenz testen - gleicher Request sollte gleiches Ergebnis liefern
curl -X POST https://your-shop.com/api/_action/custom/process-order \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_INTEGRATION_TOKEN" \
-H "Idempotency-Key: unique-request-id-123" \
-d '{"orderId": "01234567890abcdef"}'
Performance-Tipps
- Nutze DAL EntityRepository effizient – vermeide N+1 Queries durch Associations
- Cache wo möglich – besonders bei read-heavy Endpoints
- Async Processing – für zeitintensive Operations Message Queue nutzen
- Pagination – bei List-Endpoints immer Pagination implementieren
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
$criteria = new Criteria();
$criteria->setLimit(50);
$criteria->setOffset((int)$request->query->get('page', 0) * 50);
$criteria->addAssociation('customer');
$results = $this->repository->search($criteria, $context);
Fazit
Ein gut implementierter Shopware 6 API Endpoint ist sicher, idempotent und performant. Die wichtigsten Takeaways:
- Nutze Shopware's eingebaute Authentifizierung
- Implementiere Idempotenz mit Idempotency Keys
- Konsistentes Error Handling
- Rate Limiting für öffentliche Endpoints
- Gründliches Testing
Du arbeitest an einem Shopware 6 API-Integration Projekt? Ich helfe dir gerne bei der Entwicklung robuster, skalierbarer API Endpoints.