Appearance
Using the API from Node.js
This example uses the fetch function built into Node.js 18 and later, so there is nothing to install. It needs an API token and the COCOCO_URL and COCOCO_TOKEN environment variables described in Calling the CoCoCo API.
The operations
graphql
query ListJobs($first: Int, $after: String) {
listJobs(first: $first, after: $after) {
totalCount
pageInfo { hasNextPage endCursor }
edges { node { id name quantity status dueAt } }
}
}graphql
mutation CreateJob($input: CreateJobInput!) {
createJob(input: $input) {
job { id name status }
errors { path message }
}
}The program
Save this as jobs.mjs. node jobs.mjs lists every job; node jobs.mjs create creates one.
javascript
const ENDPOINT = `${process.env.COCOCO_URL.replace(/\/$/, '')}/graphql`;
const TOKEN = process.env.COCOCO_TOKEN;
const LIST_JOBS = `query ListJobs($first: Int, $after: String) {
listJobs(first: $first, after: $after) {
totalCount
pageInfo { hasNextPage endCursor }
edges { node { id name quantity status dueAt } }
}
}`;
const CREATE_JOB = `mutation CreateJob($input: CreateJobInput!) {
createJob(input: $input) {
job { id name status }
errors { path message }
}
}`;
// POST one operation and return its data. Throws on any top-level error.
async function graphql(query, variables = {}) {
const response = await fetch(ENDPOINT, {
method: 'POST',
headers: {
Authorization: `Bearer ${TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ query, variables }),
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${await response.text()}`);
}
const result = await response.json();
// Authentication, permission and query errors arrive here with HTTP 200.
if (result.errors?.length) {
throw new Error(result.errors.map((e) => e.message).join('; '));
}
return result.data;
}
// Yield every job, 100 per request.
async function* listJobs() {
let after = null;
while (true) {
const { listJobs: page } = await graphql(LIST_JOBS, { first: 100, after });
for (const edge of page.edges) yield edge.node;
if (!page.pageInfo.hasNextPage) return;
after = page.pageInfo.endCursor;
}
}
async function createJob(input) {
const { createJob: payload } = await graphql(CREATE_JOB, { input });
// Validation problems come back in the payload, not as top-level errors.
if (payload.errors.length) {
throw new Error(payload.errors.map((e) => `${e.path}: ${e.message}`).join('; '));
}
return payload.job;
}
if (process.argv[2] === 'create') {
const job = await createJob({ name: 'Business cards', quantity: 500, externalId: 'ERP-10042' });
console.log(`Created ${job.id} (${job.status})`);
} else {
for await (const job of listJobs()) {
console.log(job.id, job.status, job.quantity, job.name);
}
}Notes
graphql()throws for both kinds of failure: an HTTP error status, and a response whoseerrorsarray is not empty.listJobs()followspageInfo.endCursoruntilhasNextPageisfalse. The API returns at most 100 items per page.externalIdmust be unique in your tenant. Runningcreatetwice with the same value returns a validation error increateJob.errors.- The file uses top-level
await, which is why it has the.mjsextension.