Skip to content

GraphQL for REST users ​

If you have worked with REST APIs, most of what you know still applies. This page maps REST habits to their GraphQL equivalents, with examples against the CoCoCo API. The REST URLs shown are generic illustrations, not CoCoCo endpoints.

One endpoint instead of many ​

A REST API exposes one URL per resource:

text
GET    /jobs
GET    /jobs/123
POST   /jobs
PATCH  /jobs/123
DELETE /jobs/123
GET    /jobs/123/components

The CoCoCo API has one URL, POST https://<your-tenant-host>/graphql. The operation in the body says what to do:

RESTGraphQL
GET /jobsquery { listJobs { … } }
GET /jobs/123query { getJob(id: …) { … } }
POST /jobsmutation { createJob(input: …) { … } }
PATCH /jobs/123mutation { updateJob(id: …, input: …) { … } }
PUT /jobs/123mutation { upsertJob(input: …) { … } }
DELETE /jobs/123mutation { deleteJob(input: { id: … }) { … } }

Over-fetching and under-fetching ​

A REST resource returns a fixed shape. You often get fields you don't need (over-fetching), and you often need a second or third call for related data (under-fetching): the job, then its components, then its customer.

In GraphQL you ask for exactly the fields you need, including related records, in one request:

graphql
query JobCard {
  getJob(id: "job_01M0R249JMETNBM1Z7466MXGZD") {
    jobNumber
    name
    status
    dueAt
    billingCustomer {
      name
    }
    components {
      name
      kind
      quantity
    }
  }
}

Reading data ​

graphql
query JobsForToday {
  listJobs(
    first: 25
    filter: { dueAt: { gte: "2026-09-24T00:00:00Z", lt: "2026-09-25T00:00:00Z" } }
    sort: [{ field: PRIORITY, direction: DESC }]
  ) {
    totalCount
    edges {
      node {
        id
        name
        status
      }
    }
  }
}

Query-string parameters such as ?status=PRESS&sort=-priority become typed filter and sort arguments. Core patterns lists the operators.

Creating, updating and deleting ​

graphql
mutation CreateJob {
  createJob(input: { name: "Flyers A5", quantity: 10000 }) {
    job {
      id
    }
    errors {
      path
      message
    }
  }
}
graphql
mutation RenameJob {
  updateJob(id: "job_01M0R249JMETNBM1Z7466MXGZD", input: { name: "Flyers A5, reprint" }) {
    job {
      id
      name
    }
    errors {
      path
      message
    }
  }
}
graphql
mutation RemoveJob {
  deleteJob(input: { id: "job_01M0R249JMETNBM1Z7466MXGZD" }) {
    success
    errors {
      path
      message
    }
  }
}

updateJob behaves like PATCH: fields you leave out are not changed.

Status codes and errors ​

REST uses the HTTP status code to signal success or failure. GraphQL puts the outcome in the response body:

RESTCoCoCo GraphQL
422 Unprocessable Entity with field errorserrors { path message } in the mutation payload; the record is null
401 Unauthorized, 403 Forbiddena top-level errors entry: Authentication required, or Access denied: …
404 Not Foundget<Entity> returns null
400 Bad Requesta top-level errors entry describing the invalid query or argument

So check the body, not only the status code. See Core patterns.

Pagination ​

REST APIs page with ?page=3&per_page=50 or with cursors in Link headers. The CoCoCo API uses cursor connections: pass first (up to 100) and the previous page's endCursor as after, and stop when hasNextPage is false.

graphql
query Page($after: String) {
  listCustomers(first: 100, after: $after) {
    edges {
      node {
        id
        name
      }
    }
    pageInfo {
      hasNextPage
      endCursor
    }
  }
}

A cursor marks a position in the sort order rather than a row offset, so records added or removed before your position do not make you skip or repeat rows.

Versioning ​

REST APIs usually version in the URL (/v1/, /v2/). A GraphQL API has one schema, and clients only receive the fields they ask for, so new fields and types do not affect existing queries. Keep your queries in version control and validate them against the current schema (GET /graphql/schema) when you upgrade, so you notice any field that has changed.

Caching ​

REST responses are often cached by URL with HTTP caching headers. GraphQL requests are POSTs to one URL, so caching happens in the client instead. Client libraries such as Apollo Client (or urql with its Graphcache exchange) keep a normalised cache keyed by type and id; CoCoCo IDs are stable, so they make good cache keys.

Bulk operations ​

Instead of a batch endpoint, send several mutations in one request. They run one after the other, in the order written:

graphql
mutation AddComponents {
  cover: createComponent(input: { jobId: "job_01M0R249JMETNBM1Z7466MXGZD", name: "Cover", kind: COVER, quantity: 5000 }) {
    component {
      id
    }
    errors {
      path
      message
    }
  }
  text: createComponent(input: { jobId: "job_01M0R249JMETNBM1Z7466MXGZD", name: "Text block", kind: TEXT, quantity: 5000 }) {
    component {
      id
    }
    errors {
      path
      message
    }
  }
}

Each mutation succeeds or fails on its own. When records must be created together or not at all, use a mutation that does it in one transaction, such as createOrderGraph (see Orders and commerce). A few mutations also take lists, for example bulkUpdateOrderStatuses and upsertOperationItems.

Webhooks and polling ​

In REST you might poll GET /jobs?updated_since=… or register a webhook. With the CoCoCo API, poll the records you track and compare their updatedAt. Automation that runs inside the platform is covered in Workflow automation.

Next steps ​

Connect machines, automate workflows, and build custom apps for print production.