Skip to main content
More in Learn

JS API Client

This library provides simplifications and helpers to easily fetch data from your tenant.

So far, the available helpers are:

  • Searcher
  • Order Payment Updater
  • Order Pusher
  • Product Hydrater
  • Navigation Fetcher
  • Simple Client
  • Experimental: Mass Call Client

Installation

With NPM

npm install @crystallize/js-api-client

With Yarn

yarn add @crystallize/js-api-client

Simple Client

This is a simple client to communicate with the Crystallize APIs.

You get access to different helpers for each API:

  • catalogueApi
  • searchApi
  • orderApi
  • subscriptionApi
  • pimApi

First, you need to create the Client:

import { createClient } from '@crystallize/js-api-client';

export const CrystallizeClient = createClient({
    tenantIdentifier: 'furniture'
});

Then you can use it:

export async function fetchSomething(): Promise<Something[]> {
    const caller = CrystallizeClient.catalogueApi;
    const response = await caller(graphQLQuery, variables);
    return response.catalogue;
}

There is a live demo: https://crystallizeapi.github.io/libraries/js-api-client/call-api

Product Hydrater

Usually in the context of the Cart/Basket, you might want to keep the SKUs and/or the paths of the Variants in the basket locally and ask Crystallize to hydrate the data at some point.

There are 2 helpers for it that you get via `createProductHydrater` ! You get an object with `byPaths` or `bySkus` that are functions. The function signatures are:

function hydrater(items:string[], language:string, extraQuery: any, perProduct: (item: string, index: number) => any, perVariant: (item: string, index: number) => any);

When called both return an array of products based on the strings in the arguments (paths or SKUs) you provided.

Note: when you hydrate by SKU, the helper fetches the paths from the Search API.

There is a live demo for both:

To go even further

You might want to return more information from that function by extending the GraphQL query that is generated for you. You can do that thanks the last parameters.

Those last parameters MUST return an object that respects the logic of https://www.npmjs.com/package/json-to-graphql-query

Example:

const CrystallizeClient = createClient({
    tenantIdentifier: 'furniture'
});
const hydrater = createProductHydrater(CrystallizeClient).byPaths;
const response = await hydrater(
    [
        '/shop/bathroom-fitting/large-mounted-cabinet-in-treated-wood',
        '/shop/bathroom-fitting/mounted-bathroom-counter-with-shelf'
    ],
    'en',
    {
        tenant: {
            id: true
        },
        perVariant: {
            id: true
        },
        perProduct: {
            firstImage: {
                variants: {
                    url: true
                }
            }
        }
    }
);

With this code, you get the Products, the current tenant id, the id for each Variant, and for each product the URL of transcoded images of the first product Image.

Navigation Fetcher

In Crystallize, your Items or Topics are organized like a tree or graph, i.e. hierarchically. It's very common that you will want to build the navigation of your website following the Content Tree or the Topic Tree.

These fetchers do the heaving lifting for you. Behind the scenes, they will build a recursive GraphQL query for you.

There are 2 helpers for it that you get via `createNavigationFetcher` ! You get an object with `byFolders` or `byTopics` that are functions. The function signatures are:

function fetch(path:string, language:string, depth:number, extraQuery: any, (level:number) => any);

Note: These helpers use the children property and are therefore not paginated. You have to take this into account.

Example of Usage:

const response = await CrystallizeNavigationFetcher('/', 'en', 3).byFolders;
const response = await CrystallizeNavigationFetcher('/', 'en', 2).byTopics;

To go even further

You might want to have more information in the return from that function by extending the GraphQL query that is generated for you. You can do that thanks the last parameters.

Those last parameters MUST return an object that respects the logic of https://www.npmjs.com/package/json-to-graphql-query

Example:

    const fetch = createNavigationFetcher(CrystallizeClient).byFolders;
    const response = await fetch(
        '/',
        'en',
        3,
        {
            tenant: {
                __args: {
                    language: 'en'
                },
                name: true
            }
        },
        (level) => {
            switch (level) {
                case 0:
                    return {
                        shape: {
                            identifier: true
                        }
                    };
                case 1:
                    return {
                        createdAt: true
                    };
                default:
                    return {};
            }
        }
    );

Here you will get the navigation but also the tenant name, the shape identifier for items of depth=1 and the creation date for item of depth=2.

Order Fetcher

It is also very common to fetch an Order from Crystallize, it usually requires authentification and this helper is probably more suitable for your Service API. This fetcher does the heaving lifting like the other to simplify how to fetch orders.

There are 2 helpers for it that you get via `createOrderFetcher` ! You get an object with `byId` or `byCustomerIdentifier` that are functions.

  • byId: takes an orderId in argument and fetches the related Order for you.
  • byCustomerIdentifier: takes a customerIdentifier and fetches all the Orders (with pagination) of that customer.

Function signatures respectively are:

function byId(orderId: string, onCustomer?: any, onOrderItem?: any, extraQuery?: any);
function byId(customerIdentifier: string, extraQueryArgs?: any, onCustomer?: any, onOrderItem?: any, extraQuery?: any);

To go even further

You might want to have more information in the return from that function by extending the GraphQL query that is generated for you. You can do that thanks the last parameters.

Order Pusher

You can use the CrystallizeOrderPusher to push an order to Crystallize. This helper will validate the order and throw an exception if the input is incorrect. Also, all the Types (and the Zod JS types) are exported so you can work more efficiently.

 const caller = CrystallizeOrderPusher;
        await caller({
            customer: {
                firstName: 'William',
                lastName: 'Wallace'
            },
            cart: [
                {
                    sku: '123',
                    name: 'Bamboo Chair',
                    quantity: 3
                }
            ]
        });

This is the minimum to create an Order. Of course, the Order can be much more complete.

Order Payment Updater

You can use the CrystallizeCreateOrderPaymentUpdater to update an order with payment information in Crystallize. This helper will validate the payment and throw an exception if the input is incorrect. And all the Types (and Zod JS types) are exported so you can work more efficiently.

        const caller = CrystallizeCreateOrderPaymentUpdater;
        const result = await caller('xXxYyYZzZ', {
            payment: [
                {
                    provider: 'custom',
                    custom: {
                        properties: [
                            {
                                property: 'payment_method',
                                value: 'Crystal Coin'
                            },
                            {
                                property: 'amount',
                                value: '112358'
                            }
                        ]
                    }
                }
            ]
        });

Searcher

You can use the CrystallizeSearcher to search through the Search API in a more fancy way.

JS API Client expose a type CatalogueSearchFilter and a type catalogueSearchOrderBy that you can use in combinaison with other parameters to experience a better search.

The search function is a generator that allows you to seamlessly loop into the results while the lib is taking care of the pagination.

const CrystallizeClient = createClient({
    tenantIdentifier: 'furniture',
});

//note: you can use the catalogueFetcherGraphqlBuilder
const nodeQuery = {
    name: true,
    path: true,
};
const filter = {
    type: 'PRODUCT',
};
const orderBy = undefined;
const pageInfo = {/* customize here if needed */};

for await (const item of createSearcher(CrystallizeClient).search('en', nodeQuery, filter, orderBy, pageInfo, {
    total: 15,
    perPage: 5,
})) {
console.log(item); // what you have passed to nodeQuery
}

Mass Call Client

Sometimes, when you have many calls to do, whether they are queries or mutations you want to be able to manage them asynchronously with a good experience. This is the purpose of Mass Call Client, it will let you be asynchronous while using the client but managing the heavy lifting of lifecycle, retry, incremental increase or decrease of the pace etc.

Those are the main features:

  • Run initialSpawn requests asynchronously in a Batch. initialSpawn is the size of the batch per default
  •  if there is more than 50% of errors in the Batch, it saves the errored, and continues with a batch of 1 request.
  •  if there is less than 50% of errors in the Batch, it saves the errored, and continues with a batch of -1 request.
  •  if there is no error it +1 the number of request in a Batch, capped to maxSpawn
  •  if there is 100% of errors, it waits based on Fibonnaci increment
  •  At the end of all batches, you can retry the errored requests
  •  Optional lifecycle function onBatchDone (async)
  •  Optional lifecycle function onFailure (sync): allowing you to do something and decide to let enqueue (return true: default) or return false and re-execute right away, or any other actions.
  •  Optional lifecycle function beforeRequest (sync) execute before each request. You can return an altered request/promise
  •  Optional lifecycle function afterRequest (sync) execute after each request, you also get the result in there if needed.
async function run() {
    for (let i = 1; i <= 54; i++) {
        client.enqueue.catalogueApi(`query { catalogue { id, key${i}: name } }`);
    }

    const successes = await client.execute();
    console.log('First pass done ', successes);
    console.log('Failed Count: ' + client.failureCount());
    while (client.hasFailed()) {
        console.log('Retrying...');
        const newSuccesses = await client.retry();
        console.log('Retry pass done ', newSuccesses);
    }
    console.log('ALL DONE!');
}
run();

A full example is here: https://github.com/CrystallizeAPI/libraries/blob/main/components/js-api-client/src/examples/dump-tenant.ts

People showing thumbs up

Need further assistance?

Ask the Crystallize team or other enthusiasts in our slack community.

Join our slack community