Skip to main content

JSON:API

What you'll learn
  • Using Foscia to interact with a JSON:API

Setup

Please follow the getting started guide to set up your JSON:API action factory.

Usage

Eager loading relations

Foscia supports eager loading relations through include. Internally, it will use the JSON:API relationships inclusion features.

import { query, all, include } from '@foscia/core';

const posts = await action().run(query(Post), include('author'), all());

Filtering requests

You can filter your request using the filterBy enhancer. This function both supports key-value or object parameters.

import { query, all } from '@foscia/core';
import { filterBy } from '@foscia/jsonapi';

const posts = await action().run(
query(Post),
// Key-value pair.
filterBy('published', true),
// Object.
filterBy({ published: true }),
all(),
);

Sorting results

You can sort results using multiple enhancers: sortBy, sortByAsc and sortByDesc. sortBy supports both object and arrays parameters and will apply ascending sorting by default. sortByAsc and sortByDesc supports variadic keys parameter.

import { query, all } from '@foscia/core';
import { sortBy, sortByAsc } from '@foscia/jsonapi';

const posts = await action().run(
query(Post),
// Ascending sorting.
sortBy(['publishedAt', 'createdAt']),
// Custom sorting.
sortBy(['publishedAt', 'createdAt'], ['desc', 'asc']),
sortBy({ publishedAt: 'desc', createdAt: 'asc' }),
// Variadic keys.
sortByAsc('publishedAt', 'createdAt'),
all(),
);

Sparse fieldsets

You can filter the record fields you will retrieve using fields and fieldsFor enhancers. Those are strongly typed to your model's properties and will automatically query for properties' aliases if they are set.

fields applies for the currently targeted model, whereas fieldsFor applies for the given model.

import { query, all, include } from '@foscia/core';
import { sortBy, sortByDesc } from '@foscia/jsonapi';

const posts = await action().run(
query(Post),
include('author'),
fields('title', 'author'),
fieldsFor(User, 'username'),
all(),
);

Paginating results

Pagination is agnostic when using JSON:API. That's why Foscia propose a paginate enhancer in which you can pass any object value. This provides support of all pagination style.

You can combine this with the usingDocument utility function to retrieve the whole JSON:API document (for example when your server serialize pagination metadata in it).

import { query, all } from '@foscia/core';
import { paginate, usingDocument } from '@foscia/jsonapi';

const data = await action().run(
query(Post),
// A standard pagination.
paginate({ size: 10, number: 1 }),
// A cursor pagination.
paginate({ size: 10, after: 1 }),
all(usingDocument),
);

// The `all` result.
console.log(data.instances);
// The JSON:API document.
console.log(data.document);
// Some pagination metadata your server gives you.
console.log(data.document.meta!.page.hasMore);

Configuration recipes

Here are common configuration for @foscia/jsonapi implementation. You can read the implementation and configuration guide for more details.

You can also take a look at HTTP usage and common configuration recipes, as the JSON:API adapter is based on HTTP adapter.

Changing endpoint case

By default, JSON:API use kebab case for models and relations endpoints (e.g. favorite-posts for a favoritePosts relation). If you want to use another case for endpoints, you can use modelPathTransformer and relationPathTransformer options.

import { camelCase } from 'lodash-es';
import { makeJsonApiAdapter } from '@foscia/rest';

makeJsonApiAdapter({
modelPathTransformer: (path) => camelCase(path),
relationPathTransformer: (path) => camelCase(path),
});

Changing serialization keys case

By default, serialized and deserialized attributes and relations keep keys specified in the model. If you are using camel cased keys (e.g. firstName) but want to exchange kebab cased keys (e.g. first-name) with your API, you can use serializeKey and deserializeKey options.

import { kebabCase } from 'lodash-es';
import { makeJsonApiSerializer, makeJsonApiDeserializer } from '@foscia/rest';

makeJsonApiSerializer({
serializeKey: ({ key }) => kebabCase(key),
});

makeJsonApiDeserializer({
deserializeKey: ({ key }) => kebabCase(key),
});

Parsing URL IDs

Some API implementation may serialize records IDs as URL to the record endpoint (such as https://example.com/api/posts/1 for post 1). You can customize the deserializer to support ID and type extraction from URL ID using the pullIdentifier option.

import { makeJsonApiDeserializer } from '@foscia/rest';

makeJsonApiDeserializer({
pullIdentifier: (record) => {
// This will support IDs like `https://example.com/api/posts/1`, `/api/posts/1`, etc.
const [id, type] = String(record.id).split('/').reverse();

return { id, type };
},
});

Reference