Skip to content
On this page

Contracts

You will learn

  • Why it is important to use Contracts
  • What Contracts can do in Farfetched
  • How to create your own Contract
  • How to use third-party solutions for contracts

We believe that frontend applications should not trust remote data, it is important to validate data before using it. Farfetched provides a way to validate data using Contracts.

What is a Contract

In general Contract in Farfetched is a simple object with only a few properties that allow application to check any data and decide how it should be treated — as a success response or as a failed one.

Let's take a look at these properties:

  • isData is a function that takes any data and returns true if it is a valid data and false otherwise.
  • getErrorMessages is a function that takes any data and returns an array of strings with description of reasons why data is invalid, it would be called only if isData returned false.

That's it, any object with these three properties is a Contract.

TIP

If you are using TypeScript, isData function has to be type predicate.

How to create a Contract

Of course, you can create your own Contract by hand, let's create a simple one together.

ts
import { type Contract } from '@farfetched/core';

const numberContract: Contract<
  unknown, // it takes some unknown data
  number // and returns number if it is valid
> = {
  // it is valid if data is a number
  isData: (data): data is number => typeof data === 'number',
  // if data is not a number,
  // we return an array with description of reasons why data is invalid
  getErrorMessages: (data) => {
    return [`Expected number, got ${typeof data}`];
  },
};

So, we created a Contract that takes unknown data and checks if it is a number. We can apply this Contract to any factory:

ts
const someQuery = createQuery({
  // ...
  contract: numberContract,
});

Third-party solutions

Even though Farfetched provides a way to create your own Contract, it's way more convenient to use third-party solutions for contracts.

Runtypes

We recommend using Runtypes, it has first class TS-support, it is well-documented and has a lot of useful features. Farfetched provides an integration to use Runtype as a Contract.

ts
import { Number } from 'runtypes';
import { runtypeContract } from '@farfetched/runtypes';

const numberContract = runtypeContract(Number);

That is a complete equivalent of the previous example. For such a simple contract, it's not a big deal, but for more complex contracts, it's much more convenient to use Runtypes than to create a contract by hand.

ts
import { Record, String, Number, Union, Literal } from 'runtypes';

const characterContract = runtypeContract(
  Record({
    id: Number,
    name: String,
    status: Union(Literal('Alive'), Literal('Dead'), Literal('unknown')),
    species: String,
    type: String,
    origin: Record({ name: String, url: Url }),
    location: Record({ name: String, url: Url }),
  })
);

Other integrations

Farfetched provides integrations for the following third-party solutions:

If you are using any other solution for contracts, you can easily create a wrapper for it to case your internal contracts to Farfetched Contract. Check source code of Runtypes integration for inspiration.

Released under the MIT License.