Open-source project
badgateway/ketting avatar
badgateway/ketting

Ketting is a hypermedia REST client that follows links and caches states

The HATEOAS client for javascript

573 stars33 forksTypeScriptMIT

At a glance

What is it?
Ketting is a generic REST client with hypermedia features built on the Fetch API, working with HAL, HAL Forms, Siren, JSON:API, Collection+JSON, HTML and the HTTP Link header, and it caches resources and keeps that cache consistent. It is MIT licensed TypeScript for Node.js 20 and browsers, and it ships no browser bundle.
Who is it for?
Ketting suits a JavaScript team whose API returns links rather than a fixed set of endpoints, since following a rel, submitting a form and reading back the cached state are the primary operations rather than an afterthought, and the formats it understands cover HAL, Siren, JSON:API and plain HTML.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 18 days ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What Ketting is for, and what it does not do

Ketting is a generic REST client with hypermedia features, built on the Fetch API. It works in browsers and in Node.js.

The scope is stated in the second paragraph of the readme. It works with any JSON-based HTTP API, but it gets superpowers with formats that have links, such as HAL, HAL Forms, Siren, JSON:API, Collection+JSON, HTML and the HTTP Link header.

That sentence tells you both what the library does and what it is not. There is no server component, no cache server and no proxy. It is a client library that sits in your JavaScript application and talks to an API you already have. What it adds over a bare fetch call is the ability to navigate by relation rather than by URL, to submit hypermedia forms, to cache responses and keep that cache consistent, and to turn application/problem+json responses into exceptions.

The last of those is a small detail with real value. RFC 7807 problem responses are a common convention for API errors, and having the client raise an exception from them means error handling does not have to be reimplemented per endpoint.

The project is TypeScript, MIT licensed, published on npm as ketting, with the author listed as Evert Pot. The topics name hateoas, hal, http, rest and hypermedia-client, and the package description calls it an opinionated HATEOAS / Rest client. The opinionated part is real: the readme tells you it assumes that what you PUT to a URI has more or less the same shape as what you GET from it right after, and that assumption is what makes the caching behaviour possible.

The version history is steady rather than bursty: v9.1.0 in April 2026, v9.2.0 in September 2026 and v9.3.0 on 2026-09-28, with the last push on 2026-10-05.

Resource and State are the only two things to learn

The readme says Ketting has two primitives that are important to learn, and the reason there are only two is that everything else is built on them.

A Resource represents a single endpoint, identified by its URI. A State represents the result of a GET request, or what you would submit with a PUT request.

The distinction matters because it separates the identity of a thing from its contents. Creating a resource makes no HTTP request at all, which the readme makes explicit when it says that getting the resource does not make any HTTP request, while getting the state does a GET request. That separation is what allows a cache to exist: the resource is a handle, and the state is a snapshot that may or may not be current.

The State object carries more than data. It has uri for the absolute URI the state belongs to, and data for the parsed response body. For JSON-based formats data is the result of JSON.parse(), with HAL's _links, _embedded and _templates removed from it, and for Siren it holds the entity's properties. For text/* responses including HTML it is a string, and for any other content type it is a Blob.

That last rule about stripping the hypermedia keys is the one detail that will change how you write code against it. The link information is not thrown away, it is moved: State also exposes links for all links found in the HTTP Link header and in the response body, plus follow(), mayFollow() and followAll() for following them synchronously.

The rest of the State surface is about forms and control: action(), findAction(), actions() and hasAction() for hypermedia forms, serializeBody() to turn the state into a request body, getEmbedded() for the states of embedded resources, timestamp for when the state was created in milliseconds, headers for all HTTP headers, contentHeaders() for the subset describing the content such as Content-Type, Content-Language, ETag and Last-Modified, and clone() for a copy.

Everything on a State is synchronous, which the readme says makes it convenient to use in UI frameworks. That is a deliberate design choice rather than a limitation: a state is a value you can read during a render, and the asynchronous part is only the network request.

The cache and the assumption underneath it

The caching model is the part of Ketting that most changes how you write application code, and it comes down to a single stated assumption.

Ketting assumes that what you PUT to a URI has more or less the same shape as what you GET from it right after. This lets Ketting cache aggressively.

So a second get() of the same resource is served from the cache and returns a copy, and after a successful put() the submitted state is placed in the cache, which means the next get() reflects what you just wrote without another round trip. The readme walks through exactly this sequence with three states and a console.log that prints the title you just saved.

The copies matter as much as the caching. The returned object is a copy, so mutating it does not silently corrupt the cache, and the mutation only becomes visible to other readers once you put it. That is what makes the pattern safe in a UI framework where several components may hold the same state.

Four cache operations are documented for when the assumption does not hold. clearCache() removes the cached state of a resource, updateCache(state) stores a state without doing a request, getCache() returns the cached state synchronously or null, and refresh() does a GET request that bypasses the cache.

refresh() is the one to know about, because it is the escape hatch for a server that changed the resource without you knowing. The assumption only holds for changes you make through the client.

The ETag and Last-Modified headers are exposed through contentHeaders(), which suggests the caching layer is aware of validators, though the readme excerpt here does not spell out whether conditional requests are sent automatically.

Following links and submitting forms

The hypermedia part of the library is the reason to reach for it rather than a generic HTTP client.

The opening example in the readme shows the basic move. You follow a link with rel="author", and the comment notes that this could be an HTML link element, a HAL _links entry or an HTTP Link header. Then you grab the current state, change a property, and save it back with a PUT.

The important detail is that the three sources of links are equivalent to the client. Your code says follow('author') and does not care whether the server expressed that relation in a header, in a HAL document or in an HTML page. That is what makes a client written against Ketting portable across servers that disagree about the format, which is a real problem in the HATEOAS world where several formats coexist and no single one won.

Hypermedia forms are the other half, reached through action(), findAction(), actions() and hasAction(). The existence of four methods rather than one says the API is deliberately shaped around a task rather than around an endpoint, which matches the rest of the library. A form has a target, a method and a set of fields, and the client submits it rather than the application constructing a request body by hand.

Siren is handled specially in the state shape, since for Siren data holds the entity's properties rather than the whole action document. That is a hint that the library treats the action and entity parts of Siren separately, with actions surfacing through the form methods.

The readme also carries sections on authentication, fetch middlewares, error handling, optimizing many requests, a React section, and CORS header suggestions. The middleware point is worth noting for anyone with custom requirements: Ketting is built on the Fetch API, so a middleware that wraps fetch can sit underneath it, which is how you would add logging, retry or request correlation without forking the library.

Installing it and the packaging choices

Ketting is published on npm, and the install is a single command:

bash
npm i ketting

The requirements are short. Node.js 20 or later, because Ketting relies on the native fetch(), Request, Response, Headers, Blob and FormData globals, plus any modern browser.

The package is an ES module that targets ES2023 and exposes its entry point through package.json#exports. The default export is the same Client class, and it is also exported as Ketting:

typescript
import Ketting from 'ketting';

const client = new Ketting('https://api.example/');

The CommonJS story is the first real constraint. CommonJS code can only load Ketting with require() on Node.js versions that support requiring ES modules, and otherwise you need a dynamic import(). That is a consequence of the ESM-only decision, and it is worth checking against your build setup before you commit.

The browser story is the second. Ketting does not ship a pre-built browser bundle, so you use it through a bundler such as Vite, webpack, Rollup or esbuild, and the instructions are the same as for Node.js. What the bundler picks up automatically is the browser build, and the mechanism is the browser condition of package.json#imports, which is used to parse HTML with the browser's DOMParser instead of the Node.js HTML parser. That is a neat piece of engineering: the same import specifier resolves to a different HTML parser depending on the environment, with no configuration from you.

The runtime dependency list is short and shows what the library is made of: @badgateway/oauth2-client for the authentication support, events for the event emitter that drives the caching and events section, hal-types, http-link-header for parsing the Link header, query-string, sax, and uri-template for RFC 6570 templates.

Development is driven by a Makefile with a test target that installs Playwright and runs vitest in a headless browser, and a lint-package target that runs publint with the strict flag, which checks that the package's exports and types resolve correctly for consumers.

Where Ketting fits, and where it stops

The comparison worth making is with a plain fetch wrapper, and with a conventional REST client that expects you to know the endpoint structure.

Against a thin fetch wrapper, the difference is that Ketting has opinions about state and identity. A fetch wrapper gives you a response object and leaves you to cache it, clone it, decide when it is stale and construct the next URL. Ketting gives you a resource, a state with a parsed body, a cache that stays consistent with your writes, and link relations you can follow. For an API that returns links, that removes a lot of URL construction from application code.

Against a conventional REST client generated from an OpenAPI document, the difference is the opposite one. A generated client knows every endpoint and gives you typed methods for each, which is comfortable when the API is stable and fully described. Ketting knows nothing about your specific API until it talks to it, and it discovers the endpoints by following relations. That is the right model when the API is hypermedia and the client cannot be regenerated for every server, and the wrong one when you want compile-time guarantees about the shape of a response.

The limits are real and they come from the same source as the strengths. The aggressive caching rests on the assumption that a PUT is followed by a matching GET, which is true for most APIs and false for some, and when it is false the cache will serve something stale until you call refresh(). The optimistic concurrency handling is not described in the readme excerpt here, so conditional requests and conflict resolution would need checking in the reference documentation before you rely on them.

There is also a portability limit worth naming. The library speaks several hypermedia formats, but they are not equivalent. HAL, Siren and JSON:API have different ideas about what an action is, and the readme treats Siren specially in the state shape, which suggests the support is not perfectly uniform across formats.

And the packaging is modern and unbending: ES modules only, ES2023 target, Node.js 20 minimum, no pre-built browser bundle. None of that is controversial for a new project, and all of it is a problem for an existing CommonJS codebase without a bundler in the pipeline.

For a team building against a HAL or Siren API where the server is the source of truth about what the client can do, Ketting is a well-scoped and actively maintained library with a clear design. For a team with a fixed OpenAPI schema and typed expectations, a generated client is the better fit, and the difference in effort between the two approaches is larger than the difference in features.

Editorial conclusion

Ketting suits a JavaScript team whose API returns links rather than a fixed set of endpoints, since following a rel, submitting a form and reading back the cached state are the primary operations rather than an afterthought, and the formats it understands cover HAL, Siren, JSON:API and plain HTML. It is the wrong tool if your API is a plain JSON API with no link relations, since the extra abstraction buys nothing there, and it is the wrong tool for a CommonJS project on an older Node.js, since the package is ES module only and targets ES2023. Before adopting it, check that your server sends the link formats you need and read the deprecation warnings section of the readme for your version, since the readme is versioned with the tag.

Frequently asked questions

What is Ketting?

Ketting is a generic REST client with hypermedia features, built on the Fetch API, and it works in browsers and in Node.js. It works with any JSON-based HTTP API, and gains its extra capabilities with formats that contain links, such as HAL, HAL Forms, Siren, JSON:API, Collection+JSON, HTML and the HTTP Link header.

What are Resource and State in Ketting?

A Resource represents a single endpoint identified by its URI and makes no HTTP request when created. A State represents the result of a GET request or the body to submit with a PUT, and it exposes the parsed data, the headers, the links found in the response, the hypermedia forms, serializeBody(), getEmbedded(), a timestamp and clone().

How does Ketting handle caching?

It assumes that what you PUT to a URI has more or less the same shape as what you GET from it right after, which lets it cache aggressively. A second get() returns a copy from the cache, and after a successful put() the submitted state is placed in the cache. clearCache(), updateCache(), getCache() and refresh() give manual control.

How do I install Ketting?

Run npm i ketting. Ketting requires Node.js 20 or later because it relies on the native fetch, Request, Response, Headers, Blob and FormData globals, plus any modern browser. It is an ES module package targeting ES2023, and it does not ship a pre-built browser bundle, so browser use goes through a bundler such as Vite, webpack, Rollup or esbuild.

Can Ketting be used from CommonJS?

Only with require() on Node.js versions that support requiring ES modules, and otherwise with a dynamic import(). The default export is the same Client class and is also exported as Ketting, so import Ketting from 'ketting' works in ES module code.

Which hypermedia formats does Ketting understand?

HAL, HAL Forms, Siren, JSON:API, Collection+JSON, HTML and the HTTP Link header. Link following is format-agnostic, since follow() reads an HTML link element, a HAL _links entry or a Link header alike, and Siren is handled specially in the state, where data holds the entity's properties.

Official sources

  1. badgateway/ketting on GitHub
  2. License: MIT
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/badgateway-ketting.svg)](https://hysenlabs.com/projects/badgateway-ketting)

Was this useful?