Unverified Commit e2b9f0c7 authored by Hong Minhee's avatar Hong Minhee
Browse files
parent a4e5c52f
Loading
Loading
Loading
Loading
+5 −4
Original line number Diff line number Diff line
@@ -88,7 +88,7 @@ export default defineConfig({
        link: "https://dash.deno.com/playground/fedify-demo",
      },
      { text: "Installation", link: "/install.md" },
      { text: "Tutorial", link: "/tutorial.md" },
      { text: "In-depth tutorial", link: "/tutorial.md" },
      {
        text: "CLI toolchain",
        link: "/cli.md",
@@ -127,7 +127,8 @@ export default defineConfig({
      },
      {
        icon: {
          svg: '<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>ActivityPub</title><path d="M10.91 4.442L0 10.74v2.52L8.727 8.22v10.077l2.182 1.26zM6.545 12l-4.364 2.52 4.364 2.518zm6.545-2.52L17.455 12l-4.364 2.52zm0-5.038L24 10.74v2.52l-10.91 6.298v-2.52L21.819 12 13.091 6.96z"/></svg>',
          svg:
            '<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>ActivityPub</title><path d="M10.91 4.442L0 10.74v2.52L8.727 8.22v10.077l2.182 1.26zM6.545 12l-4.364 2.52 4.364 2.518zm6.545-2.52L17.455 12l-4.364 2.52zm0-5.038L24 10.74v2.52l-10.91 6.298v-2.52L21.819 12 13.091 6.96z"/></svg>',
        },
        link: "https://hollo.social/@fedify",
        ariaLabel: "Hollo (ActivityPub)",
@@ -181,8 +182,8 @@ export default defineConfig({
      "meta",
      {
        name: "fediverse:creator",
        content: "@fedify@hollo.social"
      }
        content: "@fedify@hollo.social",
      },
    ],
    ...plausibleScript,
  ],
+68 −22
Original line number Diff line number Diff line
@@ -4,27 +4,77 @@ prev:
  text: What is Fedify?
  link: ./intro.md
next:
  text: Tutorial
  text: In-depth tutorial
  link: ./tutorial.md
---
Installation
============

Fedify is available on [JSR] for [Deno] and on [npm] for [Node.js] and [Bun].

> [!TIP]
> We recommend using Deno or Bun (which are TypeScript-first) for the best
> experience, but you can use Node.js if you prefer.
Quick start
-----------

The easiest way to start a new Fedify project is to use the `fedify init`
command.  It creates a new directory with a minimal Fedify project template.

### CLI toolchain

First of all, you need to have the `fedify` command, the Fedify CLI toolchain,
installed on your system.  If you haven't installed it yet, please follow the
following instructions:

::: code-group

~~~~ sh [Node.js]
npm install -g @fedify/cli
~~~~

~~~~ sh [Bun]
bun install -g @fedify/cli
~~~~

~~~~ sh [Deno]
deno install -A --unstable-fs --unstable-kv --unstable-temporal -n fedify jsr:@fedify/cli
~~~~

:::

There are other ways to install the `fedify` command.  Please refer to the
[*Installation* section](./cli.md#installation) in the *CLI toolchain* docs.

### Project setup

After installing the `fedify` command, you can create a new Fedify project by
running the following command:

~~~~ sh
fedify init your-project-dir
~~~~

The above command will start a wizard to guide you through the project setup.
You can choose the JavaScript runtime, the package manager, and the web
framework you want to integrate Fedify with, and so on.  After the wizard
finishes, you will have a new Fedify project in the *your-project-dir*
directory.

For more information about the `fedify init` command, please refer to the
[*`fedify init`* section](./cli.md#fedify-init-initializing-a-fedify-project)
in the *CLI toolchain* docs.


Manual installation
-------------------

Fedify is available on [JSR] for [Deno] and on [npm] for [Bun] and [Node.js].

[JSR]: https://jsr.io/@fedify/fedify
[Deno]: https://deno.com/
[npm]: https://www.npmjs.com/package/@fedify/fedify
[Node.js]: https://nodejs.org/
[Bun]: https://bun.sh/
[Node.js]: https://nodejs.org/


Deno
----
### Deno

[Deno] is the primary runtime for Fedify.  As a prerequisite, you need to have
Deno 1.41.0 or later installed on your system.  Then you can install Fedify
@@ -35,7 +85,7 @@ deno add @fedify/fedify
~~~~

Since Fedify requires [`Temporal`] API, which is an unstable feature in Deno as
of May 2024, you need to add the `"temporal"` to the `"unstable"` field of
of July 2024, you need to add the `"temporal"` to the `"unstable"` field of
the *deno.json* file:

~~~~ json{5}
@@ -49,9 +99,16 @@ the *deno.json* file:

[`Temporal`]: https://tc39.es/proposal-temporal/docs/

### Bun

Fedify can also be used in Bun.  You can install it via the following
command:

~~~~ sh
bun add @fedify/fedify
~~~~

Node.js
-------
### Node.js

Fedify can also be used in Node.js.  As a prerequisite, you need to have Node.js
20.0.0 or later installed on your system.  Then you can install Fedify via
@@ -72,14 +129,3 @@ Fedify is an ESM-only package, so you need to add `"type": "module"` to the
  }
}
~~~~


Bun
---

Fedify can also be used in Bun.  You can install it via the following
command:

~~~~ sh
bun add @fedify/fedify
~~~~
+77 −0
Original line number Diff line number Diff line
@@ -214,6 +214,83 @@ same path.
Turned off by default.


The `~Federation.fetch()` API
-----------------------------

*This API is available since Fedify 0.6.0.*

The `Federation` object provides the `~Federation.fetch()` method to handle
incoming HTTP requests.  The `~Federation.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]:

::: code-group

~~~~ typescript [Deno]
Deno.serve(
  (request) => federation.fetch(request, { contextData: undefined })
);
~~~~

~~~~ typescript [Bun]
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 `~Federation.fetch()` method to
the Node.js' HTTP server API:

::: code-group

~~~~ sh [Node.js]
npm add @hono/node-server
~~~~

:::

And then, you can use the [`serve()`] function from the package:

::: code-group

~~~~ typescript [Node.js]
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](./integration.md).

[`Request`]: https://developer.mozilla.org/en-US/docs/Web/API/Request
[`Response`]: https://developer.mozilla.org/en-US/docs/Web/API/Response
[`window.fetch()`]: https://developer.mozilla.org/en-US/docs/Web/API/Window/fetch
[`Deno.serve()`]: https://docs.deno.com/api/deno/~/Deno.serve
[Deno]: http://deno.com/
[`Bun.serve()`]: https://bun.sh/docs/api/http#bun-serve
[Bun]: https://bun.sh/
[Node.js]: https://nodejs.org/
[@hono/node-server]: https://github.com/honojs/node-server
[`serve()`]: https://github.com/honojs/node-server?tab=readme-ov-file#usage


How the `Federation` object recognizes the domain name
------------------------------------------------------

+110 −78
Original line number Diff line number Diff line
@@ -8,14 +8,20 @@ prev:
  link: ./install.md
---

Tutorial
========
In-depth tutorial
=================

In this tutorial, we will build a small federated server that can only accept
follow requests.  Despite its simplicity, it will cover the key features of the
follow requests to understand the basic concepts of the Fedify framework.
Despite its simplicity, it will cover the key features of the
ActivityPub protocol and the Fedify framework, such as actors, sending and
receiving activities, and the inbox.

This tutorial will not use the quick start project template created by the
[`fedify init`](./cli.md#fedify-init-initializing-a-fedify-project) command.
Instead, we will start from scratch to understand how the Fedify framework
works without any boilerplate code.

As prerequisite knowledge, you should have a basic understanding of
JavaScript, command-line interfaces, and minimum experience with building
web server apps.  However, it's perfectly fine if you're not familiar with
@@ -50,18 +56,19 @@ echo '{ "unstable": ["kv", "temporal"] }' > deno.json
deno add @fedify/fedify
~~~~

~~~~ sh [Node.js]
~~~~ sh [Bun]
mkdir follow-server
cd follow-server/
echo '{ "type": "module" }' > package.json
npm add -D typescript tsx @types/node
npm add @deno/kv @fedify/fedify @hono/node-server
bun add @deno/kv @fedify/fedify
~~~~

~~~~ sh [Bun]
~~~~ sh [Node.js]
mkdir follow-server
cd follow-server/
bun add @deno/kv @fedify/fedify
echo '{ "type": "module" }' > package.json
npm add -D typescript tsx @types/node
npm add @deno/kv @fedify/fedify @hono/node-server
~~~~

:::
@@ -81,6 +88,16 @@ The above commands will create a *deno.json* (in case of Deno) or *package.json*
}
~~~~

~~~ json [Bun]
{
  "type": "module",
  "dependencies": {
    "@deno/kv": "^0.8.0",
    "@fedify/fedify": "^0.12.0"
  }
}
~~~

~~~ json [Node.js]
{
  "type": "module",
@@ -96,23 +113,34 @@ The above commands will create a *deno.json* (in case of Deno) or *package.json*
}
~~~

~~~ json [Bun]
{
  "dependencies": {
    "@deno/kv": "^0.8.0",
    "@fedify/fedify": "^0.12.0"
  }
}
~~~

:::

> [!NOTE]
> The [`"unstable"`] field in the *deno.json* file is required because Fedify
> uses [`Temporal`] API, which is an unstable feature in Deno as of
> July 2024.  By adding `"temporal"` to the `"unstable"` field, you can use the
> Fedify framework without any issues.

> [!NOTE]
> In Bun and Node.js, you need to add [`"type": "module"`] to the *package.json*
> file because Fedify is an ESM-only package.

> [!TIP]
> Do you wonder why we need to add *[tsx]* and *@types/node* in the case of
> Node.js?  It's because Fedify is written in TypeScript, and
> Node.js doesn't support TypeScript out of the box.  By adding *tsx* and
> *@types/node*, you can write TypeScript code in Node.js without any hassle.

[^2]: The actual version number may vary depending on the latest version of the
      Fedify framework as of reading this tutorial.

[Deno]: https://deno.com/
[Bun]: https://bun.sh/
[Node.js]: https://nodejs.org/
[`"unstable"`]: https://docs.deno.com/runtime/manual/tools/unstable_flags/#configuring-flags-in-deno.json
[`Temporal`]: https://tc39.es/proposal-temporal/docs/
[`"type": "module"`]: https://nodejs.org/api/packages.html#type
[tsx]: https://tsx.is/


Creating the server
@@ -131,10 +159,8 @@ Deno.serve(request =>
);
~~~~

~~~~ typescript [Node.js]
import { serve } from "@hono/node-server";

serve({
~~~~ typescript [Bun]
Bun.serve({
  port: 8000,
  fetch(request) {
    return new Response("Hello, world", {
@@ -144,8 +170,10 @@ serve({
});
~~~~

~~~~ typescript [Bun]
Bun.serve({
~~~~ typescript [Node.js]
import { serve } from "@hono/node-server";

serve({
  port: 8000,
  fetch(request) {
    return new Response("Hello, world", {
@@ -166,30 +194,30 @@ request. You can run the server by executing the following command:
deno run -A server.ts
~~~~

~~~~ sh [Node.js]
node --import tsx server.ts
~~~~

~~~~ sh [Bun]
bun server.ts
~~~~

~~~~ sh [Node.js]
node --import tsx server.ts
~~~~

::::

Now, open your web browser and navigate to <http://localhost:8000/>.  You should
see the <q>Hello, world</q> message.

As you can guess, [`Deno.serve()`] (in case of Deno), [`serve()`] (in case of
Node.js), and [`Bun.serve()`] (in case of Bun) are a function to create an HTTP
As you can guess, [`Deno.serve()`] (in case of Deno), [`Bun.serve()`] (in case
of Bun), and [`serve()`] (in case of Node.js) are a function to create an HTTP
server.  They take a callback function that receives a [`Request`] object and
returns a [`Response`] object. The `Response` object is sent back to the client.

This server is not federated yet, but it's a good starting point to build a
federated server.

[`Deno.serve()`]: https://deno.land/api?s=Deno.serve
[`serve()`]: https://github.com/honojs/node-server?tab=readme-ov-file#usage
[`Deno.serve()`]: https://docs.deno.com/api/deno/~/Deno.serve
[`Bun.serve()`]: https://bun.sh/docs/api/http#bun-serve
[`serve()`]: https://github.com/honojs/node-server?tab=readme-ov-file#usage
[`Request`]: https://developer.mozilla.org/en-US/docs/Web/API/Request
[`Response`]: https://developer.mozilla.org/en-US/docs/Web/API/Response

@@ -232,10 +260,8 @@ Deno.serve(
);
~~~~

~~~~ typescript{6} [Node.js]
import { serve } from "@hono/node-server";

serve({
~~~~ typescript{4} [Bun]
Bun.serve({
  port: 8000,
  fetch(request) {
    return federation.fetch(request, { contextData: undefined });
@@ -243,8 +269,10 @@ serve({
});
~~~~

~~~~ typescript{4} [Bun]
Bun.serve({
~~~~ typescript{6} [Node.js]
import { serve } from "@hono/node-server";

serve({
  port: 8000,
  fetch(request) {
    return federation.fetch(request, { contextData: undefined });
@@ -283,14 +311,14 @@ to the next step.
> deno add @logtape/logtape
> ~~~~
>
> ~~~~ sh [Node.js]
> npm add @logtape/logtape
> ~~~~
>
> ~~~~ sh [Bun]
> bun add @logtape/logtape
> ~~~~
>
> ~~~~ sh [Node.js]
> npm add @logtape/logtape
> ~~~~
>
> :::
>
> Then, you can set up loggers by calling [`configure()`] function at the
@@ -352,9 +380,8 @@ Deno.serve(
);
~~~~

~~~~ typescript{8-17} [Node.js]
~~~~ typescript{7-16} [Bun]
import { Federation, MemoryKvStore, Person } from "@fedify/fedify";
import { serve } from "@hono/node-server";

const federation = createFederation<void>({
  kv: new MemoryKvStore(),
@@ -371,7 +398,7 @@ federation.setActorDispatcher("/users/{handle}", async (ctx, handle) => {
  });
});

serve({
Bun.serve({
  port: 8000,
  fetch(request) {
    return federation.fetch(request, { contextData: undefined });
@@ -379,8 +406,9 @@ serve({
});
~~~~

~~~~ typescript{7-16} [Bun]
~~~~ typescript{8-17} [Node.js]
import { Federation, MemoryKvStore, Person } from "@fedify/fedify";
import { serve } from "@hono/node-server";

const federation = createFederation<void>({
  kv: new MemoryKvStore(),
@@ -397,7 +425,7 @@ federation.setActorDispatcher("/users/{handle}", async (ctx, handle) => {
  });
});

Bun.serve({
serve({
  port: 8000,
  fetch(request) {
    return federation.fetch(request, { contextData: undefined });
@@ -423,12 +451,12 @@ WebFinger for the actor. Run the server by executing the following command:
deno run -A server.ts
~~~~

~~~~ sh [Node.js]
node --import tsx server.ts
~~~~ sh [Bun]
bun run server.ts
~~~~

~~~~ sh [Bun]
bun server.ts
~~~~ sh [Node.js]
node --import tsx server.ts
~~~~

:::
@@ -557,14 +585,14 @@ To do this, you need to install the package:
deno add @hongminhee/x-forwarded-fetch
~~~~

~~~~ sh [Node.js]
npm install x-forwarded-fetch
~~~~

~~~~ sh [Bun]
bun add x-forwarded-fetch
~~~~

~~~~ sh [Node.js]
npm add x-forwarded-fetch
~~~~

:::

Then, import the package and place the `behindProxy()` middleware in front of
@@ -580,22 +608,22 @@ Deno.serve(
);
~~~~

~~~~ typescript{2,6} [Node.js]
import { serve } from "@hono/node-server";
~~~~ typescript{1,5} [Bun]
import { behindProxy } from "x-forwarded-fetch";

serve({
Bun.serve({
  port: 8000,
  fetch: behindProxy((request) => federation.fetch(request, { contextData: undefined }),
  fetch: behindProxy((request) => federation.fetch(request, { contextData: undefined })),
});
~~~~

~~~~ typescript{1,5} [Bun]
~~~~ typescript{2,6} [Node.js]
import { serve } from "@hono/node-server";
import { behindProxy } from "x-forwarded-fetch";

Bun.serve({
serve({
  port: 8000,
  fetch: behindProxy((request) => federation.fetch(request, { contextData: undefined })),
  fetch: behindProxy((request) => federation.fetch(request, { contextData: undefined }),
});
~~~~

@@ -610,12 +638,12 @@ then run the server again:
deno run -A server.ts
~~~~

~~~~ sh [Node.js]
node --import tsx server.ts
~~~~ sh [Bun]
bun run server.ts
~~~~

~~~~ sh [Bun]
bun server.ts
~~~~ sh [Node.js]
node --import tsx server.ts
~~~~

:::
@@ -793,10 +821,12 @@ federation
  });
~~~~

~~~~ typescript{15-16,19-39} [Node.js]
~~~~ typescript{15-16,19-39} [Bun]
import { serialize as encodeV8, deserialize as decodeV8 } from "node:v8";
import { openKv } from "@deno/kv";

const kv = await openKv("kv.db");  // Open the key-value store
// Open the key-value store:
const kv = await openKv("kv.db", { encodeV8, decodeV8 });

federation
  .setActorDispatcher("/users/{handle}", async (ctx, handle, key) => {
@@ -838,12 +868,10 @@ federation
  });
~~~~

~~~~ typescript{15-16,19-39} [Bun]
import { serialize as encodeV8, deserialize as decodeV8 } from "node:v8";
~~~~ typescript{15-16,19-39} [Node.js]
import { openKv } from "@deno/kv";

// Open the key-value store:
const kv = await openKv("kv.db", { encodeV8, decodeV8 });
const kv = await openKv("kv.db");  // Open the key-value store

federation
  .setActorDispatcher("/users/{handle}", async (ctx, handle, key) => {
@@ -1039,8 +1067,8 @@ Deno.serve(async (request) => {
});
~~~~

~~~~ typescript{4-18} [Node.js]
serve({
~~~~ typescript{4-18} [Bun]
Bun.serve({
  port: 8000,
  async fetch(request) {
    const url = new URL(request.url);
@@ -1065,8 +1093,8 @@ serve({
});
~~~~

~~~~ typescript{4-18} [Bun]
Bun.serve({
~~~~ typescript{4-18} [Node.js]
serve({
  port: 8000,
  async fetch(request) {
    const url = new URL(request.url);
@@ -1143,12 +1171,16 @@ Exercises
    activity.

 -  Integration with a web framework: In the above example, we hard-coded
    the home page inside the callback function passed to the `Deno.serve()`.
    Instead, you can use a web framework like [Fresh] to utilize the proper
    routing system and [JSX] templates to produce HTML.
    the home page inside the callback function passed to `Deno.serve()`
    (in case of Deno), `Bun.serve()` (in case of Bun), and `serve()` (in case
    of Node.js).  This is not enough for a real-world application as you would
    need to rendering HTML templates, handling media files, and so on.
    Instead, you can use a web framework like [Hono] or [Fresh] to utilize
    the proper routing system and [JSX] templates to produce HTML.

    See also the [*Integration* section](./manual/integration.md) in
    the manual for more details.

[Hono]: https://hono.dev/
[Fresh]: https://fresh.deno.dev/
[JSX]: https://facebook.github.io/jsx/