diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts
index 2e06d02d..5f3b29c9 100644
--- a/docs/.vitepress/config.mts
+++ b/docs/.vitepress/config.mts
@@ -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:
+ '',
},
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,
],
diff --git a/docs/install.md b/docs/install.md
index 75bfdc9f..c7ffa040 100644
--- a/docs/install.md
+++ b/docs/install.md
@@ -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
-~~~~
diff --git a/docs/manual/federation.md b/docs/manual/federation.md
index 1a0baf85..572db824 100644
--- a/docs/manual/federation.md
+++ b/docs/manual/federation.md
@@ -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
------------------------------------------------------
diff --git a/docs/tutorial.md b/docs/tutorial.md
index 140647c3..e4d96388 100644
--- a/docs/tutorial.md
+++ b/docs/tutorial.md
@@ -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 . You should
see the Hello, world 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({
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({
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/