Skip to main content

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

  1. Schema Design

    • Use clear, descriptive names
    • Include helpful descriptions
    • Keep mutations focused
  2. Performance

    • Implement dataloaders
    • Use field resolvers
    • Cache expensive operations
  3. Security

    • Validate all inputs
    • Implement proper access control
    • Rate limit operations
  4. Testing

    • Write schema tests
    • Test resolvers
    • Check error cases