Unverified Commit 61a00aeb authored by Hong Minhee's avatar Hong Minhee
Browse files

Integration docs

parent 8e88a18a
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -41,6 +41,7 @@
    "dereferenceable",
    "discoverability",
    "docloader",
    "fedi",
    "fedify",
    "fediverse",
    "Guppe",
+4 −81
Original line number Diff line number Diff line
@@ -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`
+137 −0
Original line number Diff line number Diff line
---
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
+1 −1
Original line number Diff line number Diff line
---
parent: Manual
nav_order: 9
nav_order: 10
metas:
  description: >-
    Testing a federated server app is a bit tricky because it requires a