Skip to content

What it is

fp-ts brings typed functional programming to TypeScript, with Option, Either, Task and the type-class hierarchy those structures come from.

Model absence with Option, failure with Either, and asynchrony with Task and TaskEither. The pipe function composes small total functions into larger ones.

Installation

npm install fp-ts

Getting started

The smallest useful thing you can do with it, and what each part means.

Option and Either
import { pipe } from 'fp-ts/function';
import * as O from 'fp-ts/Option';
import * as E from 'fp-ts/Either';

const parseAge = (input: string): E.Either<string, number> => {
  const n = Number(input);
  return Number.isNaN(n) ? E.left('not a number') : E.right(n);
};

const result = pipe(
  parseAge('42'),
  E.map((age) => age + 1),
  E.chain((age) => (age >= 18 ? E.right(age) : E.left('too young'))),
  E.getOrElse(() => 0),
);

// Option for values that may be absent.
const head = <A>(as: A[]): O.Option<A> =>
  as.length ? O.some(as[0]) : O.none;
pipe reads left to right, and each step only runs on the success path. Either short-circuits on the first left, which removes nested conditionals.

Advanced usage

Where the library earns its place over a simpler alternative.

TaskEither for async that can fail
import * as TE from 'fp-ts/TaskEither';

const fetchBook = (id: string): TE.TaskEither<Error, Book> =>
  TE.tryCatch(
    () => fetch(`/api/books/${id}`).then((r) => r.json()),
    (reason) => new Error(String(reason)),
  );

const program = pipe(
  fetchBook('42'),
  TE.chain((book) => book.available ? TE.right(book) : TE.left(new Error('unavailable'))),
  TE.fold(
    (error) => T.of(`failed: ${error.message}`),
    (book) => T.of(`got ${book.title}`),
  ),
);

const message = await program(); // a Task is a lazy promise — call it to run
A Task is a function returning a Promise, so it is lazy and can be retried or composed before anything executes. Note the trailing () — forgetting it is the usual beginner error.

Errors and fixes

The failures you are most likely to hit, and what actually resolves them.

Nothing runs
A Task or TaskEither was never called. They are functions; append () to execute.
Type errors deep inside pipe
pipe reports at the first mismatched step. Split the chain into named intermediates to see which one.

Best practices

  • Consider Effect for new projects; fp-ts is stable but its momentum has moved there.
  • Import modules with a namespace alias (import * as E) — the docs assume it.
  • Introduce it in one layer rather than across a whole codebase.
  • Remember Task is lazy: it must be invoked to produce a promise.

Background

Why it exists, and what it was reacting to.

Created by Giulio Canti, fp-ts introduced Haskell and Scala idioms to TypeScript. It is powerful and uncompromising; much of its community has since moved toward Effect, which offers similar guarantees with a gentler surface.