Ontwikkel een Laravel API met GraphQL Integratie en Geavanceerde Query Optimalisatie
description
Met deze prompt kan de gebruiker een moderne, flexibele API ontwerpen die complexe data queries efficiënt verwerkt via GraphQL. Het helpt bij het verbeteren van prestaties door geavanceerde query optimalisatie en zorgt voor een veilige implementatie met gespecificeerde authenticatie- en autorisatiemethoden. Dit is een waardevolle aanvulling naast REST API’s en traditionele Laravel oplossingen.
prompt
Help me een Laravel API te bouwen die GraphQL gebruikt voor flexibele dataquery’s en -mutaties. Ik wil dat je uitlegt hoe ik een GraphQL schema kan definiëren voor <specifieke data-entiteiten>, en hoe ik resolvers kan implementeren die efficiënt om ...
try_prompt
generate_helper
disclaimerOnPageApi
tags
ai_answers
provider: openai
model: gpt-4.1-nano
Natuurlijk! Hier is een uitgebreide gids voor het bouwen van een Laravel API die GraphQL gebruikt, inclusief schema-definitie, resolvers, optimalisatie, batching, en beveiliging met JWT en scopes.
---
### 1. Setup van Laravel met GraphQL
**Stap 1:** Installeer benodigde pakketten:
- GraphQL Laravel Package: bijvoorbeeld `rebing/graphql-laravel`
- JWT Authenticatie: bijvoorbeeld `tymon/jwt-auth`
```bash
composer require rebing/graphql-laravel
composer require tymon/jwt-auth
```
**Stap 2:** Publiceer configuraties en migraties:
```bash
php artisan vendor:publish --provider="Rebing\GraphQL\GraphQLServiceProvider"
php artisan vendor:publish --provider="Tymon\JWTAuth\Providers\LaravelServiceProvider"
```
**Stap 3:** Configureer JWT in `config/jwt.php` en voeg middleware toe.
---
### 2. GraphQL Schema Definiëren
**a. Types voor Product, Bestelling, Klant**
```php
// app/GraphQL/Types/ProductType.php
namespace App\GraphQL\Types;
use GraphQL\Type\Definition\Type;
use Rebing\GraphQL\Support\Type as GraphQLType;
class ProductType extends GraphQLType
{
protected $attributes = [
'name' => 'Product',
'description' => 'Een product',
];
public function fields(): array
{
return [
'id' => [
'type' => Type::nonNull(Type::int()),
],
'naam' => [
'type' => Type::nonNull(Type::string()),
],
'prijs' => [
'type' => Type::nonNull(Type::float()),
],
'categorie' => [
'type' => Type::string(),
],
];
}
}
```
Similarly voor Bestelling en Klant.
**b. Query Types**
```php
// app/GraphQL/Queries/ProductQuery.php
namespace App\GraphQL\Queries;
use GraphQL\Type\Definition\Type;
use Rebing\GraphQL\Support\Query;
use App\Models\Product;
class ProductQuery extends Query
{
protected $attributes = [
'name' => 'products',
];
public function type(): Type
{
return Type::listOf(GraphQL::type('Product'));
}
public function args(): array
{
return [
'id' => [
'name' => 'id',
'type' => Type::int(),
],
'naam' => [
'name' => 'naam',
'type' => Type::string(),
],
// voeg filters toe
];
}
public function resolve($root, $args)
{
$query = Product::query();
if (isset($args['id'])) {
$query->where('id', $args['id']);
}
if (isset($args['naam'])) {
$query->where('naam', 'like', '%' . $args['naam'] . '%');
}
return $query->get();
}
}
```
Herhaal voor bestellingen en klanten.
---
### 3. Resolvers voor complexe queries en optimalisatie
**a. Eager Loading voor N+1 Probleem**
Gebruik `with()` om gerelateerde data in één query op te halen:
```php
public function resolve($root, $args)
{
$query = Product::with('categorie');
// filters
return $query->get();
}
```
**b. Batching en DataLoader**
Gebruik DataLoader (bijvoorbeeld met `rebing/graphql-laravel` of `dataloader-php`) om meerdere gerelateerde gegevens in één batch te laden. Dit vermindert het aantal database queries bij complexe nested queries.
---
### 4. Query-optimalisatie technieken
- **Eager Loading**: voorkom N+1 problemen.
- **Indexes**: Zorg voor indexen op veelgebruikte zoekvelden.
- **Limit & Offset**: voor grote datasets, voeg paginatie toe.
- **Complexiteit beperken**: gebruik `GraphQL::setMaxQueryComplexity()` om brute force te voorkomen.
- **Caching**: cache resultaten van veelgevraagde data.
---
### 5. Batching en DataLoader
Gebruik bijvoorbeeld `rebing/graphql-laravel`'s ingebouwde batching of integreer `dataloader-php`:
```php
// Implementatie van DataLoader voor gerelateerde data
// bijvoorbeeld om bestellingen voor klanten te batchen
```
Dit vermindert het aantal database queries bij nested queries.
---
### 6. Beveiliging: JWT tokens met scopes voor toegang
**a. JWT Authenticatie**
- Voeg middleware toe aan GraphQL routes:
```php
// in routes/graphql.php
Route::group(['middleware' => ['auth:api']], function() {
// graphql endpoint
});
```
- In `app/Http/Kernel.php`, zorg dat 'auth:api' JWT gebruikt.
**b. Scopes en autorisatie**
Gebruik scopes binnen JWT tokens om verschillende rechten te definiëren:
```php
// Aanmaken token met scopes
$token = JWTAuth::fromUser($user, ['scopes' => ['producten:lees', 'bestellingen:schrijven']]);
```
**c. Verifiëren van scopes in resolvers of middleware**
Maak een middleware die controleert of de JWT token de juiste scope bevat:
```php
// app/Http/Middleware/CheckScope.php
public function handle($request, Closure $next, $scope)
{
$user = JWTAuth::parseToken()->authenticate();
$scopes = JWTAuth::getPayload()->get('scopes');
if (!in_array($scope, $scopes)) {
return response()->json(['error' => 'Onvoldoende rechten'], 403);
}
return $next($request);
}
```
En voeg deze middleware toe aan je route of resolver.
---
### Samengevat
- Definieer types en queries in GraphQL schema
- Gebruik eager loading en batching voor performantie
- Pas query complexiteit en paginatie toe
- Beveilig endpoints met JWT tokens en scopes
- Controleer scopes binnen resolvers of middleware
Wil je dat ik je help met concrete codevoorbeelden voor een specifiek onderdeel of meer uitleg over een bepaald aspect?
provider: deepseek
model: deepseek-chat
Hallo! Ik help je graag met het bouwen van een Laravel API met GraphQL. Hier is een complete uitleg in het Nederlands:
## 1. Vereisten installeren
```bash
composer require rebing/graphql-laravel
php artisan vendor:publish --provider="Rebing\GraphQL\GraphQLServiceProvider"
```
## 2. GraphQL Schema definiëren
**Product Type:**
```php
// graphql/types/ProductType.php
namespace App\GraphQL\Types;
use App\Models\Product;
use GraphQL\Type\Definition\Type;
use Rebing\GraphQL\Support\Type as GraphQLType;
class ProductType extends GraphQLType
{
protected $attributes = [
'name' => 'Product',
'description' => 'Een product',
'model' => Product::class
];
public function fields(): array
{
return [
'id' => [
'type' => Type::nonNull(Type::int()),
'description' => 'ID van het product',
],
'name' => [
'type' => Type::nonNull(Type::string()),
'description' => 'Naam van het product',
],
'price' => [
'type' => Type::nonNull(Type::float()),
'description' => 'Prijs van het product',
],
'orders' => [
'type' => Type::listOf(\GraphQL::type('Order')),
'description' => 'Bestellingen met dit product',
]
];
}
}
```
**Query Class:**
```php
// graphql/queries/ProductsQuery.php
namespace App\GraphQL\Queries;
use App\Models\Product;
use GraphQL\Type\Definition\Type;
use Rebing\GraphQL\Support\Query;
use GraphQL\Type\Definition\ResolveInfo;
use Rebing\GraphQL\Support\Facades\GraphQL;
class ProductsQuery extends Query
{
protected $attributes = [
'name' => 'products',
];
public function type(): Type
{
return Type::listOf(GraphQL::type('Product'));
}
public function args(): array
{
return [
'id' => ['name' => 'id', 'type' => Type::int()],
'name' => ['name' => 'name', 'type' => Type::string()],
];
}
public function resolve($root, $args, $context, ResolveInfo $resolveInfo)
{
$query = Product::query();
if (isset($args['id'])) {
$query->where('id', $args['id']);
}
if (isset($args['name'])) {
$query->where('name', 'like', "%{$args['name']}%");
}
return $query->get();
}
}
```
## 3. Resolvers implementeren met eager loading
```php
// graphql/types/OrderType.php
public function fields(): array
{
return [
'id' => Type::nonNull(Type::int()),
'customer' => [
'type' => \GraphQL::type('Customer'),
'resolve' => function($order, $args, $context, ResolveInfo $info) {
// Eager loading om N+1 problemen te voorkomen
return $order->customer;
}
],
'products' => [
'type' => Type::listOf(\GraphQL::type('Product')),
'resolve' => function($order, $args, $context, $info) {
return $order->products()->with('category')->get();
}
]
];
}
```
## 4. Query optimalisatie en batching
**DataLoader voor batching:**
```php
// app/GraphQL/DataLoaders/ProductDataLoader.php
namespace App\GraphQL\DataLoaders;
use App\Models\Product;
use Illuminate\Support\Facades\DB;
use GraphQL\Deferred;
class ProductDataLoader
{
public static function batchLoad(array $keys)
{
$products = Product::whereIn('id', $keys)->get()->keyBy('id');
return array_map(function($key) use ($products) {
return new Deferred(function() use ($products, $key) {
return $products[$key] ?? null;
});
}, $keys);
}
}
```
**Gebruik in resolver:**
```php
public function resolve($root, $args, $context, ResolveInfo $info)
{
$fieldName = $info->fieldName;
if ($fieldName === 'products') {
return ProductDataLoader::batchLoad([$root->id]);
}
return $root->$fieldName;
}
```
## 5. Beveiliging met JWT en scopes
**JWT Authenticatie installeren:**
```bash
composer require tymon/jwt-auth
php artisan vendor:publish --provider="Tymon\JWTAuth\Providers\LaravelServiceProvider"
php artisan jwt:secret
```
**Middleware voor GraphQL:**
```php
// app/Http/Middleware/GraphQLAuth.php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
use Tymon\JWTAuth\Facades\JWTAuth;
class GraphQLAuth
{
public function handle(Request $request, Closure $next): Response
{
try {
$token = $request->bearerToken();
if (!$token) {
return response()->json(['error' => 'Token vereist'], 401);
}
JWTAuth::setToken($token)->authenticate();
} catch (\Exception $e) {
return response()->json(['error' => 'Ongeldig token'], 401);
}
return $next($request);
}
}
```
**Scope-based autorisatie:**
```php
// app/GraphQL/Middleware/ScopeMiddleware.php
namespace App\GraphQL\Middleware;
use Closure;
use GraphQL\Type\Definition\ResolveInfo;
use Rebing\GraphQL\Support\Middleware;
use Tymon\JWTAuth\Facades\JWTAuth;
class ScopeMiddleware extends Middleware
{
public function handle($root, $args, $context, ResolveInfo $info, Closure $next)
{
$requiredScopes = $this->getRequiredScopes($info->fieldName);
if (!$this->hasScopes($requiredScopes)) {
throw new \Exception('Onvoldoende rechten');
}
return $next($root, $args, $context, $info);
}
private function getRequiredScopes(string $field): array
{
$scopes = [
'createProduct' => ['product:write'],
'updateProduct' => ['product:write'],
'deleteProduct' => ['product:write'],
'products' => ['product:read'],
'orders' => ['order:read'],
];
return $scopes[$field] ?? [];
}
private function hasScopes(array $requiredScopes): bool
{
$userScopes = JWTAuth::getPayload()->get('scopes') ?? [];
return empty($requiredScopes) ||
!empty(array_intersect($requiredScopes, $userScopes));
}
}
```
## 6. GraphQL configuratie
```php
// config/graphql.php
'schemas' => [
'default' => [
'query' => [
'products' => App\GraphQL\Queries\ProductsQuery::class,
'orders' => App\GraphQL\Queries\OrdersQuery::class,
],
'mutation' => [
'createProduct' => App\GraphQL\Mutations\CreateProductMutation::class,
],
'middleware' => [
'auth:api',
App\GraphQL\Middleware\ScopeMiddleware::class,
],
],
],
```
## 7. Query depth limiting en complexity analysis
```php
// config/graphql.php
'security' => [
'query_max_complexity' => 1000,
'query_max_depth' => 15,
'disable_introspection' => env('GRAPHQL_DISABLE_INTROSPECTION', false),
],
```
## 8. Gebruik in frontend
```graphql
query {
products {
id
name
price
orders {
id
customer {
name
email
}
}
}
}
```
## Belangrijke tips:
1. **Eager loading**: Gebruik `with()` om N+1 problemen te voorkomen
2. **Pagination**: Implementeer cursor-based pagination voor grote datasets
3. **Caching**: Gebruik Redis of memcached voor query caching
4. **Monitoring**: Monitor query performance met Laravel Telescope
5. **Validation**: Valideer alle input in mutations
Dit geeft je een solide basis voor een veilige en efficiënte GraphQL API in Laravel!