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