Skip to content

Federation

The Federation object is the main entry point of the Fedify library. It provides a set of methods to configure and run the federated server. The key features of the Federation object are as follows:

You can create a Federation object by calling createFederation() function with a configuration object:

typescript
import { 
createFederation
,
MemoryKvStore
} from "@fedify/fedify";
const
federation
=
createFederation
<void>({
kv
: new
MemoryKvStore
(),
// Omitted for brevity; see the following sections for details. });

Constructor parameters

The createFederation() function takes an object with the following properties. Some of them are required:

kv

Required. The kv property is a KvStore instance that the Federation object uses to store several kinds of cache data and to maintain the queue of outgoing activities.

KvStore is an abstract interface that represents a key-value store. For now, Fedify provides two built-in implementations of KvStore, which are MemoryKvStore and DenoKvStore classes. The MemoryKvStore class is for testing and development purposes, and the DenoKvStore class is Deno KV-backed implementation for production use (as you can guess from the name, it is only available in Deno runtime).

As separate packages, @fedify/redis provides RedisKvStore class, which is a Redis-backed implementation for production use, and @fedify/postgres provides PostgresKvStore class, which is a PostgreSQL-backed implementation for production use.

Further details are explained in the Key–value store section.

kvPrefixes

The kvPrefixes property is an object that contains the key prefixes for the cache data. The following are the key prefixes that the Federation object uses:

activityIdempotence

The key prefix used for storing whether activities have already been processed or not. ["_fedify", "activityIdempotence"] by default.

remoteDocument

The key prefix used for storing remote JSON-LD documents. ["_fedify", "remoteDocument"] by default.

publicKey

This API is available since Fedify 0.12.0.

The key prefix used for caching public keys. ["_fedify", "publicKey"] by default.

queue

This API is available since Fedify 0.5.0.

The queue property is a MessageQueue instance that the Federation object uses to maintain the queue of incoming and outgoing activities. If you don't provide this option, activities will not be queued and will be processed immediately.

MessageQueue is an abstract interface that represents a message queue. For now, Fedify provides two built-in implementations of MessageQueue, which are InProcessMessageQueue and DenoKvMessageQueue classes. The InProcessMessageQueue class is for testing and development purposes, and the DenoKvMessageQueue class is a Deno KV-backed implementation for production use (as you can guess from the name, it is only available in Deno runtime).

As separate packages, @fedify/redis provides RedisMessageQueue class, which is a Redis-backed implementation for production use, and @fedify/postgres provides PostgresMessageQueue class, which is a PostgreSQL-backed implementation for production use, and @fedify/amqp provides AmqpMessageQueue class, which is an AMQP broker-backed implementation for production use.

Further details are explained in the Message queue section.

IMPORTANT

While the queue option is optional, it is highly recommended to provide a message queue implementation in production environments. If you don't provide a message queue implementation, activities will not be queued and will be sent immediately. This can make delivery of activities unreliable and can cause performance issues.

TIP

Since Fedify 1.3.0, you can separately configure the message queue for incoming and outgoing activities by providing an object with inbox and outbox properties:

typescript
queue
: {
inbox
: new
PostgresMessageQueue
(
postgres
("postgresql://user:pass@localhost/db")
),
outbox
: new
RedisMessageQueue
(() => new
Redis
()),
}

Or, you can provide a message queue for only the inbox or outbox by omitting the other:

typescript
queue
: {
inbox
: new
PostgresMessageQueue
(
postgres
("postgresql://user:pass@localhost/db")
), // outbox is not provided; outgoing activities will not be queued. }

manuallyStartQueue

This API is available since Fedify 0.12.0.

Whether to start the task queue manually or automatically.

If true, the task queue will not start automatically and you need to manually start it by calling the Federation.startQueue() method.

If false, the task queue will start automatically as soon as the first task is enqueued.

By default, the queue starts automatically.

TIP

This option is useful when you want to separately deploy the web server and the task queue worker. In this case, you can start the task queue in the worker process, and the web server process doesn't start the task queue, but only enqueues tasks. Of course, in this case, you need to provide a MessageQueue backend that can be shared between the web server and the worker process (e.g., a Redis-backed message queue) as the queue option.

documentLoader

A JSON-LD document loader function that the Federation object uses to load remote JSON-LD documents. The function takes a URL and returns a promise that resolves to a RemoteDocument.

Usually, you don't need to set this property because the default document loader function is sufficient for most cases. The default document loader caches the loaded documents in the key-value store.

See the Getting a DocumentLoader section for details.

authenticatedDocumentLoaderFactory

This API is available since Fedify 0.4.0.

A factory function that creates an authenticated document loader function. The factory function takes the key pair of an actor and returns a document loader function that loads remote JSON-LD documents as the actor.

Usually, you don't need to set this property because the default document loader factory is sufficient for most cases. The default document loader factory intentionally doesn't cache the loaded documents in the key-value store.

See the Getting an authenticated DocumentLoader section for details.

contextLoader

This API is available since Fedify 0.8.0.

A JSON-LD context loader function that the Federation object uses to load remote JSON-LD contexts. The type of the function is the same as the documentLoader function, but their purposes are different (see also Document loader vs. context loader section).

allowPrivateAddress

This API is available since Fedify 0.15.0.

WARNING

Do not turn on this option in production environments. Disallowing fetching private network addresses is a security feature to prevent SSRF attacks.

Whether to allow fetching private network addresses in the document loader.

If turned on, documentLoader, contextLoader, and authenticatedDocumentLoaderFactory cannot be configured.

Mostly useful for testing purposes.

Turned off by default.

userAgent

This API is available since Fedify 1.3.0.

The options for making User-Agent header in the HTTP requests that Fedify makes. By default, it contains the name and version of the Fedify library, and the name and version of the JavaScript runtime, e.g.:

Fedify/1.3.0 (Deno/2.0.4)
Fedify/1.3.0 (Node.js/v22.10.0)
Fedify/1.3.0 (Bun/1.1.33)

You can customize the User-Agent header by providing options like software and url. For example, if you provide the following options:

ts
{
  software: "MyApp/1.0.0",
  url: "https://myinstance.com/"
}

The User-Agent header will be like:

MyApp/1.0.0 (Fedify/1.3.0; Deno/2.0.4; +https://myinstance.com/)

Or, you can rather provide a custom User-Agent string directly instead of an object for options.

CAUTION

This settings do not affect the User-Agent header of the HTTP requests that lookupWebFinger(), lookupObject(), and getNodeInfo() functions make, since they do not depend on the Federation object.

However, Context.lookupObject() method is affected by this settings.

outboxRetryPolicy

This API is available since Fedify 0.12.0.

The retry policy for sending activities to recipients' inboxes.

By default, this uses an exponential backoff strategy with a maximum of 10 attempts and a maximum delay of 12 hours.

You can fully customize the retry policy by providing a custom function that satisfies the RetryPolicy type. Or you can adjust the parameters of the createExponentialBackoffRetryPolicy() function, which is a default implementation of the retry policy.

inboxRetryPolicy

This API is available since Fedify 0.12.0.

The retry policy for processing incoming activities.

By default, this uses an exponential backoff strategy with a maximum of 10 attempts and a maximum delay of 12 hours.

In the same way as the outboxRetryPolicy option, you can fully customize the retry policy by providing a custom function that satisfies the RetryPolicy type. Or you can adjust the parameters of the built-in createExponentialBackoffRetryPolicy() function.

trailingSlashInsensitive

This API is available since Fedify 0.12.0.

Whether the router should be insensitive to trailing slashes in the URL paths. For example, if this option is true, /foo and /foo/ are treated as the same path.

Turned off by default.

tracerProvider

This API is available since Fedify 1.3.0.

The OpenTelemetry tracer provider that the Federation object uses to instrument various parts of Fedify for tracing. If omitted, it is configured to use the default tracer provider (i.e., trace.getTracerProvider()).

For more information, see the OpenTelemetry section.

The fetch() API

This API is available since Fedify 0.6.0.

The Federation object provides the fetch() method to handle incoming HTTP requests. The fetch() method takes an incoming Request and returns a Response.

Actually, this interface is de facto standard in the server-side JavaScript world, and it is inspired by the window.fetch() method in the browser environment.

Therefore, you can pass it to the Deno.serve() function in Deno, and the Bun.serve() function in Bun:

typescript
Deno.
serve
(
(
request
) =>
federation
.
fetch
(
request
, {
contextData
:
undefined
})
);
typescript
Bun
.
serve
({
fetch
: (
request
) =>
federation
.
fetch
(
request
, {
contextData
:
undefined
}),
})

However, in case of Node.js, it has no built-in server API that takes fetch() callback function like Deno or Bun. Instead, you need to use @hono/node-server package to adapt the fetch() method to the Node.js' HTTP server API:

sh
npm add @hono/node-server

And then, you can use the serve() function from the package:

typescript
import { 
serve
} from "@hono/node-server";
serve
({
fetch
: (
request
) =>
federation
.
fetch
(
request
, {
contextData
:
undefined
}),
})

NOTE

Although a Federation object can be directly passed to the HTTP server APIs, you would usually integrate it with a web framework. For details, see the Integration section.

How the Federation object recognizes the domain name

The Federation object recognizes the domain name of the server by the Host header of the incoming HTTP requests. The Host header is a standard HTTP header that contains the domain name of the server.

However, the Host header is not always reliable because it can be bypassed by a reverse proxy or a load balancer. If you use a reverse proxy or a load balancer, you should configure it to pass the original Host header to the server.

Or you can make the Federation object recognize the domain name by looking at the X-Forwarded-Host header instead of the Host header using the x-forwarded-fetch middleware. To use the x-forwarded-fetch middleware, install the package:

sh
deno add jsr:@hongminhee/x-forwarded-fetch
sh
npm install x-forwarded-fetch
sh
bun add x-forwarded-fetch

Then, import the package and place the behindProxy() middleware in front of the Federation.fetch() method:

typescript
import { 
behindProxy
} from "@hongminhee/x-forwarded-fetch";
Deno.
serve
(
behindProxy
(
request
=>
federation
.
fetch
(
request
, {
contextData
:
undefined
}))
);
typescript
import { 
serve
} from "@hono/node-server";
import {
behindProxy
} from "x-forwarded-fetch";
serve
({
fetch
:
behindProxy
((
request
) =>
federation
.
fetch
(
request
, {
contextData
:
undefined
})),
});
typescript
import { 
behindProxy
} from "x-forwarded-fetch";
Bun
.
serve
({
fetch
:
behindProxy
((
request
) =>
federation
.
fetch
(
request
, {
contextData
:
undefined
})),
});

TIP

When your Federation object is integrated with a web framework, you should place the behindProxy() middleware in front of the framework's fetch() method, not the Federation.fetch() method.

Integrating with web frameworks

Federation is designed to be used together with web frameworks. For details, see the Integration section.

TContextData

The Federation class is a generic class that takes a type parameter named TContextData. The TContextData type is the type of the context data, which is shared among the actor dispatcher, inbox listener, and other callback functions. The TContextData type can be void if you don't need to share any context data, but it can be any type if you need to share context data.

For example, if you want to share a database connection among the actor dispatcher, inbox listener, and other callback functions, you can set the TContextData type to the type of the database connection:

typescript
import { FreshContext } from "$fresh/server.ts";
import { federation } from "../federation.ts"; // Import the `Federation` object
import { DatabasePool, getPool } from "./database.ts";

export async function handler(request: Request, context: FreshContext) {
  return federation.fetch(request, {
    contextData: getPool(),  
    onNotFound: context.next.bind(context),
    onNotAcceptable: async (request: Request) => {
      // Omitted for brevity
    }
  });
};

The Context.data is passed to registered callback functions as their first parameter within the Context object:

typescript
federation
.
setActorDispatcher
("/users/{handle}", async (
ctx
,
handle
) => {
// There is a database connection in `ctx.data`. });

Another example is to determine the virtual host of the server based on the incoming HTTP request. See the next section for details.

Virtual hosting

This API is available since Fedify 0.12.0.

You may want to support multiple domains on the same server, so-called virtual hosts. To determine the virtual host of the server based on the incoming HTTP request, you can use Context.host that contains the virtual host information:

typescript
federation
.
setActorDispatcher
("/@{handle}", (
ctx
,
handle
) => {
const
fullHandle
= `${
handle
}@${
ctx
.
host
}`;
// Omitted for brevity });

You can access the virtual host information in the actor dispatcher, inbox listener, and other callback functions.

See also the Getting the base URL section in the Context document.