Appearance
Using the API from TypeScript
This example uses the standard fetch function and hand-written types for the two responses, so it needs no GraphQL client library. It runs on Node.js 18 or later and 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.ts. For the Node.js types, install typescript and @types/node (npm install --save-dev typescript @types/node). Node.js 24 runs the file directly (node jobs.ts, or node jobs.ts create to create a job); with older versions, compile it first (npx tsc --target es2022 --module nodenext --types node jobs.ts) and run node jobs.js.
typescript
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 }
}
}`;
interface GraphQLResponse<T> {
data?: T;
errors?: { message: string }[];
}
interface Job {
id: string; // a JobID such as "job_01M0SYJHB7FX6BKYVZZ7EAG4Y8"
name: string;
quantity: number;
status: string; // a JobStatus value such as "DRAFT" or "CONFIRMED"
dueAt: string | null; // ISO 8601
}
interface ListJobsData {
listJobs: {
totalCount: number | null;
pageInfo: { hasNextPage: boolean; endCursor: string | null };
edges: { node: Job }[];
};
}
interface CreateJobData {
createJob: {
job: Pick<Job, 'id' | 'name' | 'status'> | null;
errors: { path: string; message: string }[];
};
}
// POST one operation and return its data. Throws on any top-level error.
async function graphql<T>(query: string, variables: Record<string, unknown> = {}): Promise<T> {
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()) as GraphQLResponse<T>;
// Authentication, permission and query errors arrive here with HTTP 200.
if (result.errors?.length || !result.data) {
throw new Error(result.errors?.map((e) => e.message).join('; ') ?? 'No data in response');
}
return result.data;
}
// Yield every job, 100 per request.
async function* listJobs(): AsyncGenerator<Job> {
let after: string | null = null;
while (true) {
const { listJobs: page }: ListJobsData = await graphql<ListJobsData>(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: { name: string; quantity: number; externalId?: string }) {
const { createJob: payload } = await graphql<CreateJobData>(CREATE_JOB, { input });
// Validation problems come back in the payload, not as top-level errors.
if (payload.errors.length || !payload.job) {
throw new Error(payload.errors.map((e) => `${e.path}: ${e.message}`).join('; '));
}
return payload.job;
}
async function main(): Promise<void> {
if (process.argv[2] === 'create') {
const job = await createJob({ name: 'Business cards', quantity: 500, externalId: 'ERP-10042' });
console.log(`Created ${job.id} (${job.status})`);
return;
}
for await (const job of listJobs()) {
console.log(job.id, job.status, job.quantity, job.name);
}
}
main().catch((error: unknown) => {
console.error(error);
process.exitCode = 1;
});Notes
- The types describe only the fields the two operations select. To generate them from the schema instead, download the SDL with
curl -o schema.graphql https://<your-tenant-host>/graphql/schemaand give that file to a code generator such as GraphQL Code Generator. statusis typed asstring. See JobStatus for the values.graphql()throws for both kinds of failure: an HTTP error status, and a response whoseerrorsarray is not empty.externalIdmust be unique in your tenant. Runningcreatetwice with the same value returns a validation error increateJob.errors.