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: 'ActivityPub', + svg: + 'ActivityPub', }, 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/