GraphQL Overview
Introduction
OX Agry's API is built using GraphQL with the OverblogGraphQLBundle for Symfony. This guide covers the implementation details and best practices.
Schema Organization
config/graphql/
├── types/
│ ├── Query.types.yaml
│ ├── Mutation.types.yaml
│ ├── Machinery.types.yaml
│ ├── Booking.types.yaml
│ └── User.types.yaml
└── schema.graphql
Basic Types
Object Types
type Machinery {
id: ID!
name: String!
type: MachineryType!
description: String
hourlyRate: Float!
available: Boolean!
owner: User!
location: Location!
features: [Feature!]!
}
type User {
id: ID!
name: String!
email: String!
role: UserRole!
machinery: [Machinery!]
bookings: [Booking!]
}
type Booking {
id: ID!
machinery: Machinery!
user: User!
startTime: DateTime!
endTime: DateTime!
status: BookingStatus!
totalCost: Float!
}
Enums
enum MachineryType {
TRACTOR
HARVESTER
PLANTER
IRRIGATOR
}
enum BookingStatus {
PENDING
CONFIRMED
IN_PROGRESS
COMPLETED
CANCELLED
}
enum UserRole {
FARMER
PROVIDER
ADMIN
}
Queries
Definition
type Query {
machinery(id: ID!): Machinery
searchMachinery(input: MachinerySearchInput!): [Machinery!]!
myBookings: [Booking!]!
providerStatistics: ProviderStats!
}
input MachinerySearchInput {
type: MachineryType
location: LocationInput!
dateRange: DateRangeInput
maxPrice: Float
}
Implementation
namespace App\GraphQL\Resolver;
class MachineryResolver implements QueryInterface
{
public function __construct(
private MachineryService $machineryService
) {}
public function machinery(string $id): ?Machinery
{
return $this->machineryService->findById($id);
}
public function searchMachinery(array $input): array
{
return $this->machineryService->search($input);
}
}
Mutations
Definition
type Mutation {
createBooking(input: CreateBookingInput!): BookingPayload!
updateMachineryStatus(input: UpdateMachineryStatusInput!): Machinery!
cancelBooking(id: ID!): BookingPayload!
}
input CreateBookingInput {
machineryId: ID!
startTime: DateTime!
endTime: DateTime!
location: LocationInput!
}
type BookingPayload {
booking: Booking!
success: Boolean!
message: String
}
Implementation
namespace App\GraphQL\Mutation;
class BookingMutation
{
public function __construct(
private BookingService $bookingService
) {}
public function createBooking(array $input): array
{
$booking = $this->bookingService->create($input);
return [
'booking' => $booking,
'success' => true,
'message' => 'Booking created successfully'
];
}
}
Authentication & Authorization
Context Building
namespace App\GraphQL\Context;
class ContextBuilder implements ContextBuilderInterface
{
public function build(array $context): array
{
return [
'user' => $context['token']->getUser(),
'roles' => $context['token']->getRoles(),
];
}
}
Field Level Security
# config/graphql/types/Mutation.types.yaml
Mutation:
fields:
createBooking:
access: '@=isAuthenticated()'
resolve: '@=mutation("App\\GraphQL\\Mutation\\BookingMutation::createBooking", [args["input"]])'
Error Handling
Custom Error Types
type Error {
message: String!
code: String!
path: [String!]
}
interface MutationResponse {
success: Boolean!
errors: [Error!]
}
Error Implementation
namespace App\GraphQL\Error;
class BookingError extends Error
{
public function getCategories(): array
{
return ['booking'];
}
}
Best Practices
-
Schema Design
- Use clear, descriptive names
- Include helpful descriptions
- Keep mutations focused
-
Performance
- Implement dataloaders
- Use field resolvers
- Cache expensive operations
-
Security
- Validate all inputs
- Implement proper access control
- Rate limit operations
-
Testing
- Write schema tests
- Test resolvers
- Check error cases