Skip to content

GraphQL for REST Users

If you're experienced with REST APIs, this guide will help you understand GraphQL by relating it to familiar REST concepts and showing how to leverage your existing knowledge in this new paradigm.

Core Conceptual Differences

Single Endpoint vs Multiple Endpoints

REST APIs:

GET /api/v1/users/123
GET /api/v1/users/123/orders
GET /api/v1/orders/456/items
POST /api/v1/users
PUT /api/v1/users/123
DELETE /api/v1/users/123

GraphQL:

POST /graphql

All operations go through a single endpoint, with the operation type and data requirements specified in the request body.

HTTP Methods vs Operation Types

REST: Uses HTTP methods to indicate operation type

  • GET for reading
  • POST for creating
  • PUT/PATCH for updating
  • DELETE for removing

GraphQL: Uses operation types in the query

  • query for reading data
  • mutation for modifying data
  • subscription for real-time updates

Request/Response Comparison

Fetching User Data

REST Request:

http
GET /api/v1/users/123
Accept: application/json

REST Response:

json
{
  "id": 123,
  "name": "John Doe",
  "email": "john@example.com",
  "phone": "555-1234",
  "address": {
    "street": "123 Main St",
    "city": "Springfield",
    "zip": "12345"
  },
  "preferences": {
    "newsletter": true,
    "notifications": false
  },
  "created_at": "2024-01-15T10:30:00Z"
}

GraphQL Request:

http
POST /graphql
Content-Type: application/json

{
  "query": "{ getUser(id: 123) { id name email } }"
}

GraphQL Response:

json
{
  "data": {
    "getUser": {
      "id": "123",
      "name": "John Doe",
      "email": "john@example.com"
    }
  }
}

The Over-fetching Problem

In the REST example above, you received all user data even if you only needed the name and email. GraphQL solves this by letting you specify exactly what fields you need.

The Under-fetching Problem

REST: Multiple requests needed

http
GET /api/v1/print-jobs/456        # Get job details
GET /api/v1/customers/789         # Get customer info
GET /api/v1/devices/101          # Get assigned device

GraphQL: Single request

http
POST /graphql
Content-Type: application/json

{
  "query": "{
    getPrintJob(id: 456) {
      id
      title
      status
      customer {
        name
        email
      }
      assignedDevice {
        name
        status
        currentLoad
      }
    }
  }"
}

CRUD Operations Comparison

Reading Data

REST:

http
# Get all print jobs
GET /api/v1/print-jobs

# Get specific job
GET /api/v1/print-jobs/123

# Get jobs with filters
GET /api/v1/print-jobs?status=PENDING&limit=10&offset=20

GraphQL:

graphql
# Get all print jobs with specific fields
{
  printJobs {
    id
    title
    status
  }
}

# Get specific job
{
  getPrintJob(id: "123") {
    id
    title
    status
    customer {
      name
    }
  }
}

# Get jobs with filters and pagination
{
  printJobs(
    filter: { status: PENDING }
    first: 10
    after: "cursor123"
  ) {
    edges {
      node {
        id
        title
        status
      }
      cursor
    }
    pageInfo {
      hasNextPage
      endCursor
    }
  }
}

Creating Data

REST:

http
POST /api/v1/print-jobs
Content-Type: application/json

{
  "title": "Business Cards",
  "customerId": 456,
  "priority": "HIGH"
}

GraphQL:

graphql
mutation CreatePrintJob {
  createPrintJob(input: {
    title: "Business Cards"
    customerId: "456"
    priority: HIGH
  }) {
    result {
      id
      title
      status
    }
    errors {
      message
      field
    }
  }
}

Updating Data

REST:

http
PUT /api/v1/print-jobs/123
Content-Type: application/json

{
  "status": "COMPLETED",
  "completedAt": "2024-01-15T15:30:00Z"
}

GraphQL:

graphql
mutation UpdatePrintJob {
  updatePrintJob(
    id: "123"
    input: {
      status: COMPLETED
      completedAt: "2024-01-15T15:30:00Z"
    }
  ) {
    result {
      id
      status
      completedAt
    }
    errors {
      message
    }
  }
}

Deleting Data

REST:

http
DELETE /api/v1/print-jobs/123

GraphQL:

graphql
mutation DeletePrintJob {
  destroyPrintJob(id: "123") {
    result {
      id
      title
    }
    errors {
      message
    }
  }
}

Error Handling Comparison

REST Error Response

http
HTTP/1.1 404 Not Found
Content-Type: application/json

{
  "error": {
    "code": "RESOURCE_NOT_FOUND",
    "message": "Print job with ID 123 not found",
    "details": {
      "resource": "print_job",
      "id": "123"
    }
  }
}

GraphQL Error Response

json
{
  "data": {
    "getPrintJob": null
  },
  "errors": [
    {
      "message": "Print job with ID 123 not found",
      "extensions": {
        "code": "PRINT_JOB_NOT_FOUND",
        "id": "123"
      },
      "path": ["getPrintJob"]
    }
  ]
}

Key Differences:

  • GraphQL always returns HTTP 200 (unless there's a server error)
  • Errors are part of the response body, not HTTP status codes
  • Partial data can be returned even when some fields error
  • Multiple errors can be returned for different fields

Caching Strategies

REST Caching

http
# REST responses can use HTTP caching headers
HTTP/1.1 200 OK
Cache-Control: max-age=300, public
ETag: "abc123"
Last-Modified: Mon, 15 Jan 2024 10:30:00 GMT

{
  "id": 123,
  "name": "John Doe"
}

GraphQL Caching

GraphQL caching is more complex because:

  1. Single endpoint means HTTP cache-control headers aren't as effective
  2. Different queries to the same endpoint need different caching strategies

Solutions:

  • Normalized caching: Libraries like Apollo Client cache individual objects
  • Persisted queries: Cache query strings on the server
  • Field-level caching: Cache at the resolver level
javascript
// Apollo Client normalized cache
{
  "User:123": {
    "id": "123",
    "name": "John Doe",
    "email": "john@example.com"
  },
  "PrintJob:456": {
    "id": "456",
    "title": "Business Cards",
    "customer": { "__ref": "User:123" }
  }
}

Pagination Patterns

REST Pagination

Offset-based:

http
GET /api/v1/print-jobs?limit=20&offset=40

Cursor-based:

http
GET /api/v1/print-jobs?limit=20&cursor=abc123

GraphQL Pagination

GraphQL standardizes on cursor-based pagination with the Relay connection specification:

graphql
{
  printJobs(first: 20, after: "cursor123") {
    edges {
      node {
        id
        title
        status
      }
      cursor
    }
    pageInfo {
      hasNextPage
      hasPreviousPage
      startCursor
      endCursor
    }
  }
}

Versioning Strategies

REST API Versioning

URL Versioning:

/api/v1/users
/api/v2/users

Header Versioning:

http
GET /api/users
Accept: application/vnd.api+json;version=2

GraphQL Versioning

GraphQL encourages versionless APIs through field evolution:

graphql
type User {
  id: ID!
  name: String!
  email: String!

  # Add new fields without breaking existing queries
  phone: String

  # Deprecate fields instead of removing them
  username: String @deprecated(reason: "Use 'name' field instead")
}

Benefits:

  • No need to maintain multiple API versions
  • Clients can migrate gradually
  • Backward compatibility is maintained

Real-time Updates

REST Approaches

Polling:

javascript
// Check for updates every 5 seconds
setInterval(async () => {
  const response = await fetch('/api/v1/print-jobs/123');
  const job = await response.json();
  updateUI(job);
}, 5000);

WebSockets:

javascript
const ws = new WebSocket('ws://localhost:8080/job-updates');
ws.onmessage = (event) => {
  const update = JSON.parse(event.data);
  updateUI(update);
};

Server-Sent Events:

javascript
const eventSource = new EventSource('/api/v1/jobs/123/events');
eventSource.onmessage = (event) => {
  const update = JSON.parse(event.data);
  updateUI(update);
};

GraphQL Subscriptions

graphql
subscription PrintJobUpdates {
  printJobUpdated(filter: { id: "123" }) {
    id
    title
    status
    progress
    estimatedCompletion
  }
}

Advantages:

  • Same query language for real-time data
  • Built into the GraphQL specification
  • Integrated with existing type system
  • Client can specify exactly what data to receive in updates

Migration Strategies

1. GraphQL Wrapper Approach

Start by wrapping existing REST endpoints with GraphQL:

graphql
type Query {
  # New GraphQL-native endpoint
  printJobs(filter: PrintJobFilter): [PrintJob!]!

  # Wrapper around existing REST endpoint
  legacyGetPrintJobs(
    status: String
    limit: Int
    offset: Int
  ): LegacyPrintJobResponse
}

2. Gradual Field Migration

Move fields from REST to GraphQL gradually:

graphql
type PrintJob {
  # Already migrated to GraphQL
  id: ID!
  title: String!
  status: JobStatus!

  # Still calls REST endpoint internally
  customer: Customer @restField(endpoint: "/api/v1/customers/{customerId}")
}

3. Parallel APIs

Run both REST and GraphQL APIs simultaneously:

javascript
// REST client code (existing)
const job = await restClient.get(`/api/v1/print-jobs/${id}`);

// GraphQL client code (new)
const { data } = await graphqlClient.query({
  query: gql`
    query GetPrintJob($id: ID!) {
      getPrintJob(id: $id) {
        id
        title
        status
      }
    }
  `,
  variables: { id }
});

Common REST Patterns in GraphQL

1. Resource Relationships

REST: Multiple requests or embedded resources

http
GET /api/v1/print-jobs/123
GET /api/v1/customers/456
GET /api/v1/print-jobs/123?include=customer,files

GraphQL: Natural relationship traversal

graphql
{
  getPrintJob(id: "123") {
    title
    customer {
      name
      email
    }
    files {
      filename
      size
    }
  }
}

2. Bulk Operations

REST: Batch endpoints

http
POST /api/v1/print-jobs/bulk
Content-Type: application/json

{
  "operations": [
    { "action": "create", "data": {...} },
    { "action": "update", "id": 123, "data": {...} }
  ]
}

GraphQL: Multiple mutations in one request

graphql
mutation BulkOperations {
  createJob1: createPrintJob(input: {...}) {
    result { id }
    errors { message }
  }

  updateJob2: updatePrintJob(id: "123", input: {...}) {
    result { id status }
    errors { message }
  }
}

3. Search and Filtering

REST: Query parameters

http
GET /api/v1/print-jobs?status=PENDING&customer=123&sort=created_at&order=desc

GraphQL: Structured input types

graphql
{
  printJobs(
    filter: {
      status: PENDING
      customerId: "123"
    }
    sort: {
      field: CREATED_AT
      direction: DESC
    }
  ) {
    id
    title
    status
  }
}

CoCore-Specific Examples

Device Management

REST Approach:

http
GET /api/v1/devices
GET /api/v1/devices/printer1/status
GET /api/v1/devices/printer1/jobs
GET /api/v1/devices/printer1/maintenance

GraphQL Approach:

graphql
{
  devices {
    id
    name
    type
    status
    currentJobs {
      id
      title
      progress
    }
    maintenanceSchedule {
      nextService
      lastService
      alerts {
        type
        message
      }
    }
  }
}

Order Processing

REST: Sequential API calls

javascript
// 1. Create order
const order = await post('/api/v1/orders', orderData);

// 2. Add items
for (const item of items) {
  await post(`/api/v1/orders/${order.id}/items`, item);
}

// 3. Calculate pricing
const pricing = await post(`/api/v1/orders/${order.id}/calculate`);

// 4. Apply discounts
await put(`/api/v1/orders/${order.id}`, { discount: pricing.discount });

GraphQL: Single mutation

graphql
mutation CreateCompleteOrder {
  createOrder(input: {
    customer: "123"
    items: [
      { productId: "456", quantity: 500 }
      { productId: "789", quantity: 1000 }
    ]
    applyAutoDiscounts: true
  }) {
    result {
      id
      total
      items {
        product { name }
        quantity
        unitPrice
        total
      }
      discounts {
        type
        amount
      }
    }
    errors {
      message
      field
    }
  }
}

Performance Considerations

REST Performance Issues

N+1 Problem:

javascript
// Get all print jobs
const jobs = await get('/api/v1/print-jobs');

// Then get customer for each job (N+1 queries)
for (const job of jobs) {
  job.customer = await get(`/api/v1/customers/${job.customerId}`);
}

Over-fetching:

javascript
// Only need job titles, but get full objects
const jobs = await get('/api/v1/print-jobs');
const titles = jobs.map(job => job.title);

GraphQL Solutions

Efficient Data Loading:

graphql
# DataLoader prevents N+1 queries behind the scenes
{
  printJobs {
    title
    customer {
      name  # Batched loading of customers
    }
  }
}

Request Exactly What You Need:

graphql
{
  printJobs {
    title  # Only fetch titles
  }
}

Best Practices for REST Developers

1. Think in Terms of Data Requirements

REST Mindset: "What endpoint do I call?" GraphQL Mindset: "What data do I need?"

2. Use Fragments for Reusability

Similar to REST client helper functions:

graphql
fragment JobSummary on PrintJob {
  id
  title
  status
  priority
  estimatedCompletion
}

query GetActiveJobs {
  activeJobs: printJobs(filter: { status: [PENDING, PRINTING] }) {
    ...JobSummary
  }
}

query GetCompletedJobs {
  completedJobs: printJobs(filter: { status: COMPLETED }) {
    ...JobSummary
    completedAt
  }
}

3. Handle Errors Gracefully

Unlike REST where you might have global error handlers based on HTTP status codes, GraphQL requires more granular error handling:

javascript
const { data, errors } = await graphqlClient.query({
  query: GET_PRINT_JOB,
  variables: { id: '123' }
});

if (errors) {
  // Handle GraphQL errors
  errors.forEach(error => {
    console.error(`Error in ${error.path}: ${error.message}`);
  });
}

if (data?.getPrintJob) {
  // Handle successful data
  updateUI(data.getPrintJob);
} else {
  // Handle null data (possibly due to errors)
  showErrorMessage("Job not found");
}

4. Leverage Type Safety

GraphQL's strong type system provides benefits similar to REST API documentation, but enforced at runtime:

typescript
// Generated types from GraphQL schema
interface PrintJob {
  id: string;
  title: string;
  status: JobStatus;
  customer: Customer;
}

// Type-safe queries
const job: PrintJob = await client.request(GET_PRINT_JOB, { id: '123' });

Conclusion

GraphQL offers significant advantages over REST for complex applications like CoCore:

  • Efficiency: Fetch exactly what you need in a single request
  • Flexibility: Clients control data requirements
  • Real-time: Built-in subscription support
  • Type Safety: Strong schema-based type system
  • Developer Experience: Excellent tooling and introspection

Your REST experience translates well to GraphQL—many concepts like resources, CRUD operations, and data relationships are similar. The main shift is from endpoint-thinking to data-requirement thinking.

Start with simple queries, gradually migrate existing REST endpoints, and leverage GraphQL's flexibility to build more efficient integrations with CoCore's manufacturing systems.

Next Steps

  • Learn about GraphQL tooling for development
  • Explore the GraphQL introduction for deeper concepts
  • Try converting your existing REST API calls to GraphQL queries
  • Experiment with CoCore's GraphQL playground
  • Set up your first GraphQL subscriptions for real-time updates

Connect. Combine. Collaborate.
The pioneering open integration platform, dedicated to transforming connectivity in the printing industry.