Skip to content

Commit

Permalink
Integration docs
Browse files Browse the repository at this point in the history
  • Loading branch information
dahlia committed Apr 3, 2024
1 parent 8e88a18 commit 61a00ae
Show file tree
Hide file tree
Showing 4 changed files with 143 additions and 82 deletions.
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
"dereferenceable",
"discoverability",
"docloader",
"fedi",
"fedify",
"fediverse",
"Guppe",
Expand Down
85 changes: 4 additions & 81 deletions docs/manual/federation.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,88 +129,11 @@ local development. However, it must be disabled in production.
Turned off by default.


Integrating with a web framework
--------------------------------
Integrating with web frameworks
-------------------------------

The `Federation` object is designed to be integrated with a web framework
such as [Fresh]. By integrating, you can handle only federation-related
requests with the `Federation` object, and handle other requests with
the web framework.

Web frameworks usually provide a way to intercept requests and handle them
in the middle, which is so-called <dfn>middleware</dfn>. If your web framework
has a middleware feature, you can use it to intercept federation-related
requests and handle them with the `Federation` object.

For example, if you use the Fresh web framework, [you can define a middleware
in a *routes/_middleware.ts* file.][fresh-middleware] The following is an
example of how to integrate the `Federation` object with Fresh:

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

export async function handler(request: Request, context: FreshContext) {
return await federation.fetch(request, {
// Wonder what is `contextData`? See the next section for details.
contextData: undefined,

// If the `federation` object finds a request not responsible for it
// (i.e., not a federation-related request), it will call the `next`
// provided by the Fresh framework to continue the request handling
// by the Fresh:
onNotFound: context.next.bind(context),

// Similar to `onNotFound`, but slightly more tricky one.
// When the `federation` object finds a request not acceptable type-wise
// (i.e., a user-agent doesn't want JSON-LD), it will call the `next`
// provided by the Fresh framework so that it renders HTML if there's some
// page. Otherwise, it will simply return a 406 Not Acceptable response.
// This kind of trick enables the Fedify and Fresh to share the same routes
// and they do content negotiation depending on `Accept` header:
async onNotAcceptable(_request: Request) {
const response = await context.next();
if (response.status !== 404) return response;
return new Response("Not acceptable", {
status: 406,
headers: {
"Content-Type": "text/plain",
Vary: "Accept",
},
});
},
});
}
~~~~

In some cases, your web framework may not represent requests and responses
as [`Request`] and [`Response`] objects. In that case, you need to convert
the request and response objects to the appropriate types that the `Federation`
object can handle.

> [!NOTE]
> The above example artificially shows a verbose way to integrate
> the `Federation` object with Fresh, so that a user of other web frameworks
> can understand the concept. In practice, you can define a middleware
> using `integrateHandler()` function from `@fedify/fedify/x/fresh` module:
>
> ~~~~ typescript
> import { federation } from "../federation.ts"; // Import the `Federation` object
> import { integrateHandler } from "@fedify/fedify/x/fresh";
>
> export const handler = integrateHandler(federation, () => undefined);
> ~~~~
> [!TIP]
> In theory, you can directly pass `Federation.fetch()` to the [`Deno.serve()`]
> function, but you probably wouldn't want to do that because you want to handle
> other requests with the web framework.
[Fresh]: https://fresh.deno.dev/
[fresh-middleware]: https://fresh.deno.dev/docs/concepts/middleware
[`Request`]: https://developer.mozilla.org/en-US/docs/Web/API/Request
[`Response`]: https://developer.mozilla.org/en-US/docs/Web/API/Response
[`Deno.serve()`]: https://deno.land/api?unstable&s=Deno.serve
`Federation` is designed to be used together with web frameworks. For details,
see the [*Integration* section](./integration.md).


`TContextData`
Expand Down
137 changes: 137 additions & 0 deletions docs/manual/integration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
---
parent: Manual
nav_order: 9
metas:
description: >-
Fedify is designed to be used together with web frameworks. This document
explains how to integrate Fedify with web frameworks.
---

Integration
===========

Fedify is designed to be used together with web frameworks. This document
explains how to integrate Fedify with web frameworks.


Hono
----

[Hono] is a fast, lightweight, and Web standard-compliant server framework for
TypeScript. Fedify has the *x/hono* module that provides a middleware to
integrate Fedify with Hono:

~~~~ typescript
import { Federation } from "@fedify/fedify";
import { federation } from "@fedify/fedify/x/hono";
import { Hono } from "hono";

const fedi = new Federation<string>({
// Omitted for brevity; see the related section for details.
});

const app = new Hono();
app.use(federation(fedi, (ctx) => "context data"));
~~~~

[Hono]: https://hono.dev/


Fresh
-----

[Fresh] is a full stack modern web framework for Deno. Fedify has the *x/fresh*
module that provides a middleware to integrate Fedify with Fresh.
Put the following code in your *routes/_middleware.ts* file:

~~~~ typescript
import { Federation } from "@fedify/fedify";
import { integrateHandler } from "@fedify/fedify/x/fresh";

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

// This is the entry point to the Fedify middleware from the Fresh framework:
export const handler = integrateHandler(
federation,
(req, ctx) => "context data",
);

~~~~

[Fresh]: https://fresh.deno.dev/


Custom middleware
-----------------

Even if you are using a web framework that is not officially supported by
Fedify, you can still integrate Fedify with the framework by creating a custom
middleware (unless the framework does not support middleware).

Web frameworks usually provide a way to intercept incoming requests and outgoing
responses in the middle, which is so-called <dfn>middleware</dfn>. If your
web framework has a middleware feature, you can use it to intercept
federation-related requests and handle them with the `Federation` object.

The key is to create a middleware that calls the `Federation.fetch()` method
with the incoming request and context data, and then sends the response from
Fedify to the client. At this point, you can use `onNotFound` and
`onNotAcceptable` callbacks to forward the request to the next middleware.

The following is an example of a custom middleware for a hypothetical web
framework:

~~~~ typescript
import { Federation } from "@fedify/fedify";

export type Middleware = (
request: Request,
next: (request: Request) => Promise<Response>
) => Promise<Response>;

export function createFedifyMiddleware<TContextData>(
federation: Federation<TContextData>,
contextDataFactory: (request: Request) => TContextData,
): Middleware {
return async (request, next) => {
return await federation.fetch(request, {
contextData: contextDataFactory(request),

// If the `federation` object finds a `request` not responsible for it
// (i.e., not a federation-related request), it will call the `next`
// provided by the web framework to continue the request handling by
// the web framework:
onNotFound: async (request) => await next(request),

// Similar to `onNotFound`, but slightly more tickly one.
// When the `federation` object finds a `request` not acceptable type-wise
// (i.e., a user-agent doesn't want JSON-LD), it will call the `next`
// provided by the web framework so that it renders HTML if there's some
// page. Otherwise, it will simply respond with `406 Not Acceptable`.
// This trick enables the Fedify and the web framework to share the same
// routes and they do content negotiation depending on `Accept` header:
onNotAcceptable: async (request) => {
const response = await next(request);
if (response.status !== 404) return response;
return new Response("Not Acceptable", {
status: 406,
headers: {
"Content-Type": "text/plain",
Vary: "Accept"
},
})
}
});
};
}
~~~~

In some cases, your web framework may not represent requests and responses
as [`Request`] and [`Response`] objects. In that case, you need to convert
the request and response objects to the appropriate types that the `Federation`
object can handle.

[`Request`]: https://developer.mozilla.org/en-US/docs/Web/API/Request
[`Response`]: https://developer.mozilla.org/en-US/docs/Web/API/Response
2 changes: 1 addition & 1 deletion docs/manual/test.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
parent: Manual
nav_order: 9
nav_order: 10
metas:
description: >-
Testing a federated server app is a bit tricky because it requires a
Expand Down

0 comments on commit 61a00ae

Please sign in to comment.