@fedify/relay: ActivityPub relay for Fedify
This package is available since Fedify 2.0.0.
This package provides ActivityPub relay functionality for the Fedify ecosystem, enabling the creation and management of relay servers that can forward activities between federated instances.
For comprehensive documentation on building and operating relay servers, see the Relay server section in the Fedify manual.
What is an ActivityPub relay?
ActivityPub relays are infrastructure components that help small instances participate effectively in the federated social network by acting as intermediary servers that distribute public content without requiring individual actor-following relationships. When an instance subscribes to a relay, all public posts from that instance are forwarded to all other subscribed instances, creating a shared pool of federated content.
Relay protocols
This package supports two popular relay protocols used in the fediverse:
Mastodon-style relay
The Mastodon-style relay protocol uses LD signatures for activity verification and follows the Public collection. This protocol is widely supported by Mastodon and many other ActivityPub implementations.
Key features:
- Direct activity relaying with proper content types (
Create,Update,Delete,Move) - LD signature verification and generation
- Follows the ActivityPub Public collection
- Simple subscription mechanism via
Followactivities
LitePub-style relay
The LitePub-style relay protocol uses bidirectional following relationships
and wraps activities in Announce activities for distribution.
Key features:
- Reciprocal following between relay and subscribers
- Activities wrapped in
Announcefor distribution - Two-phase subscription (pending → accepted)
- Enhanced federation capabilities
Installation
::: code-group
deno add jsr:@fedify/relay
npm add @fedify/relay
pnpm add @fedify/relay
yarn add @fedify/relay
bun add @fedify/relay
:::
Usage
Creating a relay
Here's a simple example of creating a relay server using the factory function:
import { createRelay } from "@fedify/relay";
import { MemoryKvStore } from "@fedify/fedify";
// Create a Mastodon-style relay
const relay = createRelay("mastodon", {
kv: new MemoryKvStore(),
origin: "https://relay.example.com",
// Required: Set a subscription handler to approve/reject subscriptions
subscriptionHandler: async (ctx, actor) => {
// For an open relay, simply return true
// return true;
// Or implement custom approval logic:
const domain = new URL(actor.id!).hostname;
const blockedDomains = ["spam.example", "blocked.example"];
return !blockedDomains.includes(domain);
},
});
// Serve the relay
Deno.serve((request) => relay.fetch(request));You can also create a LitePub-style relay by changing the type:
const relay = createRelay("litepub", {
kv: new MemoryKvStore(),
origin: "https://relay.example.com",
subscriptionHandler: async (ctx, actor) => true,
});Subscription handling
The subscriptionHandler is required and determines whether to approve or
reject subscription requests. For an open relay that accepts all subscriptions:
const relay = createRelay("mastodon", {
kv: new MemoryKvStore(),
origin: "https://relay.example.com",
subscriptionHandler: async (ctx, actor) => true, // Accept all
});You can also implement custom approval logic:
const relay = createRelay("mastodon", {
kv: new MemoryKvStore(),
origin: "https://relay.example.com",
subscriptionHandler: async (ctx, actor) => {
// Example: Only allow subscriptions from specific domains
const domain = new URL(actor.id!).hostname;
const allowedDomains = ["mastodon.social", "fosstodon.org"];
return allowedDomains.includes(domain);
},
});Managing followers
The relay provides methods to query and manage followers without exposing internal storage details.
Listing all followers
for await (const follower of relay.listFollowers()) {
console.log(`Follower: ${follower.actorId}`);
console.log(`State: ${follower.state}`);
console.log(`Actor name: ${follower.actor.name}`);
console.log(`Actor type: ${follower.actor.constructor.name}`);
}Getting a specific follower
const follower = await relay.getFollower("https://mastodon.example.com/users/alice");
if (follower) {
console.log(`Found follower in state: ${follower.state}`);
console.log(`Actor username: ${follower.actor.preferredUsername}`);
console.log(`Inbox: ${follower.actor.inboxId?.href}`);
} else {
console.log("Follower not found");
}Integration with web frameworks
The relay's fetch() method returns a standard Response object, making it
compatible with any web framework that supports the Fetch API. Here's an
example with Hono:
import { Hono } from "hono";
import { createRelay } from "@fedify/relay";
import { MemoryKvStore } from "@fedify/fedify";
const app = new Hono();
const relay = createRelay("mastodon", {
kv: new MemoryKvStore(),
origin: "https://relay.example.com",
subscriptionHandler: async (ctx, actor) => true,
});
app.use("*", async (c) => {
return await relay.fetch(c.req.raw);
});
export default app;How it works
The relay operates by:
- Actor registration: The relay presents itself as a Service actor at
/users/relay - Subscription: Instances subscribe to the relay by sending a
Followactivity - Approval: The relay's subscription handler determines whether to
approve the subscription (responds with
AcceptorReject) - Forwarding: When a subscribed instance sends activities (
Create,Update,Delete,Move) to the relay's inbox, the relay forwards them to all other subscribed instances - Unsubscription: Instances can unsubscribe by sending an
Undoactivity wrapping their originalFollowactivity
Storage requirements
The relay requires a key–value store to persist:
- Subscriber list and their Follow activity IDs
- Subscriber actor information
- Relay's cryptographic key pairs (RSA and Ed25519)
Any KvStore implementation from Fedify can be used, including:
MemoryKvStore(for development/testing)DenoKvStore(Deno KV)RedisKvStore(Redis)PostgresKvStore(PostgreSQL)MysqlKvStore(MySQL/MariaDB)SqliteKvStore(SQLite)
For production use, choose a persistent storage backend like Redis, PostgreSQL, or MySQL/MariaDB. See the Fedify documentation on key–value stores for more details.
API reference
createRelay()
Factory function to create a relay instance.
function createRelay(
type: "mastodon" | "litepub",
options: RelayOptions
): RelayParameters:
type: The type of relay to create ("mastodon"or"litepub")options: Configuration options for the relay
Returns: A Relay instance
Relay
Public interface for ActivityPub relay implementations.
Methods
fetch(request: Request): Promise<Response>: Handle incoming HTTP requestslistFollowers(): AsyncIterableIterator<RelayFollower>: Lists all followers of the relaygetFollower(actorId: string): Promise<RelayFollower | null>: Gets a specific follower by actor IDgetActorUri(): Promise<URL>: Gets the URI of the relay actorgetSharedInboxUri(): Promise<URL>: Gets the shared inbox URI of the relay
Relay types
The relay type is specified when calling createRelay():
"mastodon": Mastodon-compatible relay using direct activity forwarding, immediate subscription approval, and LD signatures"litepub": LitePub-compatible relay using bidirectional following, activities wrapped inAnnounce, and two-phase subscription
RelayOptions
Configuration options for the relay:
kv: KvStore(required): Key–value store for persisting relay dataorigin: string(required): Relay's origin URL (e.g.,"https://relay.example.com")name?: string: Relay's display name (defaults to"ActivityPub Relay")subscriptionHandler: SubscriptionRequestHandler(required): Handler for subscription approval/rejectiondocumentLoaderFactory?: DocumentLoaderFactory: Custom document loader factoryauthenticatedDocumentLoaderFactory?: AuthenticatedDocumentLoaderFactory: Custom authenticated document loader factoryqueue?: MessageQueue: Message queue for background activity processing
SubscriptionRequestHandler
A function that determines whether to approve a subscription request:
type SubscriptionRequestHandler = (
ctx: Context<RelayOptions>,
clientActor: Actor,
) => Promise<boolean>Parameters:
ctx: The Fedify context object with relay optionsclientActor: The actor requesting to subscribe
Returns:
trueto approve the subscriptionfalseto reject the subscription
RelayFollower
A follower of the relay with validated Actor instance:
interface RelayFollower {
readonly actorId: string;
readonly actor: Actor;
readonly state: "pending" | "accepted";
}Properties:
actorId: The actor ID (URL) of the followeractor: The validated Actor objectstate: The follower's state ("pending"or"accepted")