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:
- Registering an actor dispatcher
- Registering inbox listeners
- Registering collections
- Registering object dispatchers
- Creating a
Context
object - Maintaining a queue of outgoing activities
- Registering a NodeInfo dispatcher
You can create a Federation
object by calling createFederation()
function with a configuration object:
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:
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:
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:
{
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:
Deno.serve(
(request) => federation.fetch(request, { contextData: undefined })
);
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:
npm add @hono/node-server
And then, you can use the serve()
function from the package:
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:
deno add jsr:@hongminhee/x-forwarded-fetch
npm install x-forwarded-fetch
bun add x-forwarded-fetch
Then, import the package and place the behindProxy()
middleware in front of the Federation.fetch()
method:
import { behindProxy } from "@hongminhee/x-forwarded-fetch";
Deno.serve(
behindProxy(request => federation.fetch(request, { contextData: undefined }))
);
import { serve } from "@hono/node-server";
import { behindProxy } from "x-forwarded-fetch";
serve({
fetch: behindProxy((request) => federation.fetch(request, { contextData: undefined })),
});
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:
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:
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:
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.