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 Federation constructor function takes an object with the following properties. Some of them are required:

kv

Required. The ~FederationParameters.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. Currently, there are two implementations of KvStore, which are the 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). However, you can define your own KvStore implementation if you want to use a different key-value store.[1]

kvPrefixes

The ~FederationParameters.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.

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 outgoing activities. If you don't provide this option, activities will not be queued and will be sent immediately.

MessageQueue is an abstract interface that represents a message queue. Currently, there are only two implementations of MessageQueue, which are the 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). However, you can define your own MessageQueue implementation if you want to use a different message queue.[1:1]

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.

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).

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 @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, key) => {
  // 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

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 set the TContextData type to a type that contains the virtual host information:

typescript
import { createFederation } from "@fedify/fedify";

export interface VirtualHost {
  host: string;
}

export function parseVirtualHost(url: URL | string): VirtualHost {
  url = typeof url === "string" ? new URL(url) : url;
  return { host: url.hostname };
}

const federation = createFederation<VirtualHost>({
  // Omitted for brevity; see the related section for details.
});

Then, you can pass the virtual host information to the Federation.fetch() as the contextData option:

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

serve({
  fetch(request) {
    return federation.fetch(request, {
      contextData: parseVirtualHost(request.url), 
    });
  }
})
typescript
Bun.serve({
  fetch(request) {
    return federation.fetch(request, {
      contextData: parseVirtualHost(request.url), 
    });
  }
});

Now you can access the virtual host information in the actor dispatcher, inbox listener, and other callback functions:

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

  1. We are welcome to contributions of KvStore implementations for other key-value stores. ↩︎ ↩︎