Appearance
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/componentsThe CoCoCo API has one URL, POST https://<your-tenant-host>/graphql. The operation in the body says what to do:
| REST | GraphQL |
|---|---|
GET /jobs | query { listJobs { … } } |
GET /jobs/123 | query { getJob(id: …) { … } } |
POST /jobs | mutation { createJob(input: …) { … } } |
PATCH /jobs/123 | mutation { updateJob(id: …, input: …) { … } } |
PUT /jobs/123 | mutation { upsertJob(input: …) { … } } |
DELETE /jobs/123 | mutation { 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:
| REST | CoCoCo GraphQL |
|---|---|
422 Unprocessable Entity with field errors | errors { path message } in the mutation payload; the record is null |
401 Unauthorized, 403 Forbidden | a top-level errors entry: Authentication required, or Access denied: … |
404 Not Found | get<Entity> returns null |
400 Bad Request | a 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.