diff --git a/packages/backend/package.json b/packages/backend/package.json
index 9e50d4d9fa..81e7888b84 100644
--- a/packages/backend/package.json
+++ b/packages/backend/package.json
@@ -21,20 +21,19 @@
 		"@tensorflow/tfjs-node": "4.1.0"
 	},
 	"dependencies": {
-		"@bull-board/api": "4.3.1",
-		"@bull-board/koa": "4.3.1",
-		"@bull-board/ui": "4.3.1",
 		"@discordapp/twemoji": "14.0.2",
-		"@elastic/elasticsearch": "7.17.0",
-		"@koa/cors": "3.3.0",
-		"@koa/multer": "3.0.0",
-		"@koa/router": "9.0.1",
+		"@fastify/accepts": "4.0.1",
+		"@fastify/cors": "8.2.0",
+		"@fastify/multipart": "7.3.0",
+		"@fastify/static": "6.5.0",
+		"@fastify/view": "7.1.2",
 		"@nestjs/common": "9.2.0",
 		"@nestjs/core": "9.2.0",
 		"@nestjs/testing": "9.2.0",
 		"@peertube/http-signature": "1.7.0",
 		"@sinonjs/fake-timers": "10.0.0",
 		"@syuilo/aiscript": "0.11.1",
+		"accepts": "^1.3.8",
 		"ajv": "8.11.2",
 		"archiver": "5.3.1",
 		"autobind-decorator": "2.4.0",
@@ -54,6 +53,7 @@
 		"date-fns": "2.29.3",
 		"deep-email-validator": "0.1.21",
 		"escape-regexp": "0.0.1",
+		"fastify": "4.10.0",
 		"feed": "4.2.2",
 		"file-type": "18.0.0",
 		"fluent-ffmpeg": "2.1.2",
@@ -69,20 +69,10 @@
 		"json5-loader": "4.0.1",
 		"jsonld": "8.1.0",
 		"jsrsasign": "10.6.1",
-		"koa": "2.13.4",
-		"koa-bodyparser": "4.3.0",
-		"koa-favicon": "2.1.0",
-		"koa-json-body": "5.3.0",
-		"koa-logger": "3.2.1",
-		"koa-mount": "4.0.0",
-		"koa-send": "5.0.1",
-		"koa-slow": "2.1.0",
-		"koa-views": "7.0.2",
 		"mfm-js": "0.23.0",
 		"mime-types": "2.1.35",
 		"misskey-js": "0.0.14",
 		"ms": "3.0.0-canary.1",
-		"multer": "1.4.4",
 		"nested-property": "4.0.0",
 		"node-fetch": "3.3.0",
 		"nodemailer": "6.8.0",
@@ -129,6 +119,7 @@
 		"ulid": "2.3.0",
 		"unzipper": "0.10.11",
 		"uuid": "9.0.0",
+		"vary": "1.1.2",
 		"web-push": "3.5.0",
 		"websocket": "1.0.34",
 		"ws": "8.11.0",
@@ -138,6 +129,7 @@
 		"@redocly/openapi-core": "1.0.0-beta.114",
 		"@swc/core": "1.3.20",
 		"@swc/jest": "0.2.23",
+		"@types/accepts": "1.3.5",
 		"@types/archiver": "5.3.1",
 		"@types/bcryptjs": "2.4.2",
 		"@types/bull": "4.10.0",
@@ -149,17 +141,6 @@
 		"@types/jsdom": "20.0.1",
 		"@types/jsonld": "1.5.8",
 		"@types/jsrsasign": "10.5.4",
-		"@types/koa": "2.13.5",
-		"@types/koa-bodyparser": "4.3.8",
-		"@types/koa-cors": "0.0.2",
-		"@types/koa-favicon": "2.0.21",
-		"@types/koa-logger": "3.1.2",
-		"@types/koa-mount": "4.0.1",
-		"@types/koa-send": "4.1.3",
-		"@types/koa-views": "7.0.0",
-		"@types/koa__cors": "3.3.0",
-		"@types/koa__multer": "2.0.4",
-		"@types/koa__router": "8.0.11",
 		"@types/mime-types": "2.1.1",
 		"@types/node": "18.11.9",
 		"@types/node-fetch": "3.0.3",
@@ -182,6 +163,7 @@
 		"@types/tmp": "0.2.3",
 		"@types/unzipper": "0.10.5",
 		"@types/uuid": "8.3.4",
+		"@types/vary": "1.1.0",
 		"@types/web-push": "3.3.2",
 		"@types/websocket": "1.0.5",
 		"@types/ws": "8.5.3",
diff --git a/packages/backend/src/@types/koa-json-body.d.ts b/packages/backend/src/@types/koa-json-body.d.ts
deleted file mode 100644
index 2971807d15..0000000000
--- a/packages/backend/src/@types/koa-json-body.d.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-declare module 'koa-json-body' {
-	import type { Middleware } from 'koa';
-
-	interface IKoaJsonBodyOptions {
-		strict: boolean;
-		limit: string;
-		fallback: boolean;
-	}
-
-	function koaJsonBody(opt?: IKoaJsonBodyOptions): Middleware;
-
-	namespace koaJsonBody {} // Hack
-
-	export = koaJsonBody;
-}
diff --git a/packages/backend/src/@types/koa-slow.d.ts b/packages/backend/src/@types/koa-slow.d.ts
deleted file mode 100644
index d048822efe..0000000000
--- a/packages/backend/src/@types/koa-slow.d.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-declare module 'koa-slow' {
-	import type { Middleware } from 'koa';
-
-	interface ISlowOptions {
-		url?: RegExp;
-		delay?: number;
-	}
-
-	function slow(options?: ISlowOptions): Middleware;
-
-	namespace slow {} // Hack
-
-	export = slow;
-}
diff --git a/packages/backend/src/boot/index.ts b/packages/backend/src/boot/index.ts
index fbf9e73e09..f4daf30690 100644
--- a/packages/backend/src/boot/index.ts
+++ b/packages/backend/src/boot/index.ts
@@ -52,6 +52,7 @@ if (!envOption.quiet) {
 process.on('uncaughtException', err => {
 	try {
 		logger.error(err);
+		console.trace(err);
 	} catch { }
 });
 
diff --git a/packages/backend/src/core/CaptchaService.ts b/packages/backend/src/core/CaptchaService.ts
index acfa7d5910..b2bc24ac2c 100644
--- a/packages/backend/src/core/CaptchaService.ts
+++ b/packages/backend/src/core/CaptchaService.ts
@@ -45,9 +45,13 @@ export class CaptchaService {
 		return await res.json() as CaptchaResponse;
 	}	
 	
-	public async verifyRecaptcha(secret: string, response: string): Promise<void> {
-		const result = await this.getCaptchaResponse('https://www.recaptcha.net/recaptcha/api/siteverify', secret, response).catch(e => {
-			throw `recaptcha-request-failed: ${e}`;
+	public async verifyRecaptcha(secret: string, response: string | null | undefined): Promise<void> {
+		if (response == null) {
+			throw 'recaptcha-failed: no response provided';
+		}
+
+		const result = await this.getCaptchaResponse('https://www.recaptcha.net/recaptcha/api/siteverify', secret, response).catch(err => {
+			throw `recaptcha-request-failed: ${err}`;
 		});
 
 		if (result.success !== true) {
@@ -56,9 +60,13 @@ export class CaptchaService {
 		}
 	}
 
-	public async verifyHcaptcha(secret: string, response: string): Promise<void> {
-		const result = await this.getCaptchaResponse('https://hcaptcha.com/siteverify', secret, response).catch(e => {
-			throw `hcaptcha-request-failed: ${e}`;
+	public async verifyHcaptcha(secret: string, response: string | null | undefined): Promise<void> {
+		if (response == null) {
+			throw 'hcaptcha-failed: no response provided';
+		}
+
+		const result = await this.getCaptchaResponse('https://hcaptcha.com/siteverify', secret, response).catch(err => {
+			throw `hcaptcha-request-failed: ${err}`;
 		});
 
 		if (result.success !== true) {
@@ -67,9 +75,13 @@ export class CaptchaService {
 		}
 	}
 
-	public async verifyTurnstile(secret: string, response: string): Promise<void> {
-		const result = await this.getCaptchaResponse('https://challenges.cloudflare.com/turnstile/v0/siteverify', secret, response).catch(e => {
-			throw `turnstile-request-failed: ${e}`;
+	public async verifyTurnstile(secret: string, response: string | null | undefined): Promise<void> {
+		if (response == null) {
+			throw 'turnstile-failed: no response provided';
+		}
+	
+		const result = await this.getCaptchaResponse('https://challenges.cloudflare.com/turnstile/v0/siteverify', secret, response).catch(err => {
+			throw `turnstile-request-failed: ${err}`;
 		});
 
 		if (result.success !== true) {
diff --git a/packages/backend/src/core/remote/activitypub/ApRendererService.ts b/packages/backend/src/core/remote/activitypub/ApRendererService.ts
index 38850fd127..38a92567c3 100644
--- a/packages/backend/src/core/remote/activitypub/ApRendererService.ts
+++ b/packages/backend/src/core/remote/activitypub/ApRendererService.ts
@@ -674,7 +674,7 @@ export class ApRendererService {
 	 * @param last URL of last page (optional)
 	 * @param orderedItems attached objects (optional)
 	 */
-	public renderOrderedCollection(id: string | null, totalItems: any, first?: string, last?: string, orderedItems?: Record<string, unknown>[]) {
+	public renderOrderedCollection(id: string | null, totalItems: any, first?: string, last?: string, orderedItems?: IObject[]) {
 		const page: any = {
 			id,
 			type: 'OrderedCollection',
diff --git a/packages/backend/src/env.ts b/packages/backend/src/env.ts
index 1b678edc44..d7c8304b47 100644
--- a/packages/backend/src/env.ts
+++ b/packages/backend/src/env.ts
@@ -6,7 +6,6 @@ const envOption = {
 	verbose: false,
 	withLogTime: false,
 	quiet: false,
-	slow: false,
 };
 
 for (const key of Object.keys(envOption) as (keyof typeof envOption)[]) {
diff --git a/packages/backend/src/misc/create-temp.ts b/packages/backend/src/misc/create-temp.ts
index fa88769de0..429977669e 100644
--- a/packages/backend/src/misc/create-temp.ts
+++ b/packages/backend/src/misc/create-temp.ts
@@ -18,7 +18,7 @@ export function createTempDir(): Promise<[string, () => void]> {
 			(e, path, cleanup) => {
 				if (e) return rej(e);
 				res([path, cleanup]);
-			}
+			},
 		);
 	});
 }
diff --git a/packages/backend/src/misc/fastify-reply-error.ts b/packages/backend/src/misc/fastify-reply-error.ts
new file mode 100644
index 0000000000..4e987175e2
--- /dev/null
+++ b/packages/backend/src/misc/fastify-reply-error.ts
@@ -0,0 +1,11 @@
+// https://www.fastify.io/docs/latest/Reference/Reply/#async-await-and-promises
+export class FastifyReplyError extends Error {
+	public message: string;
+	public statusCode: number;
+
+	constructor(statusCode: number, message: string) {
+		super(message);
+		this.message = message;
+		this.statusCode = statusCode;
+	}
+}
diff --git a/packages/backend/src/server/ActivityPubServerService.ts b/packages/backend/src/server/ActivityPubServerService.ts
index 2e7bd4dcb2..9689a623fd 100644
--- a/packages/backend/src/server/ActivityPubServerService.ts
+++ b/packages/backend/src/server/ActivityPubServerService.ts
@@ -1,8 +1,9 @@
 import { Inject, Injectable } from '@nestjs/common';
-import Router from '@koa/router';
-import json from 'koa-json-body';
+import { FastifyInstance, FastifyRequest, FastifyReply, FastifyPluginOptions } from 'fastify';
+import fastifyAccepts from '@fastify/accepts';
 import httpSignature from '@peertube/http-signature';
 import { Brackets, In, IsNull, LessThan, Not } from 'typeorm';
+import accepts from 'accepts';
 import { DI } from '@/di-symbols.js';
 import type { FollowingsRepository, NotesRepository, EmojisRepository, NoteReactionsRepository, UserProfilesRepository, UserNotePiningsRepository, UsersRepository } from '@/models/index.js';
 import * as url from '@/misc/prelude/url.js';
@@ -56,14 +57,15 @@ export class ActivityPubServerService {
 		private userKeypairStoreService: UserKeypairStoreService,
 		private queryService: QueryService,
 	) {
+		this.createServer = this.createServer.bind(this);
 	}
 
-	private setResponseType(ctx: Router.RouterContext) {
-		const accept = ctx.accepts(ACTIVITY_JSON, LD_JSON);
+	private setResponseType(request: FastifyRequest, reply: FastifyReply): void {
+		const accept = request.accepts().type([ACTIVITY_JSON, LD_JSON]);
 		if (accept === LD_JSON) {
-			ctx.response.type = LD_JSON;
+			reply.type(LD_JSON);
 		} else {
-			ctx.response.type = ACTIVITY_JSON;
+			reply.type(ACTIVITY_JSON);
 		}
 	}
 
@@ -80,31 +82,34 @@ export class ActivityPubServerService {
 		return this.apRendererService.renderCreate(await this.apRendererService.renderNote(note, false), note);
 	}
 
-	private inbox(ctx: Router.RouterContext) {
+	private inbox(request: FastifyRequest, reply: FastifyReply) {
 		let signature;
 
 		try {
-			signature = httpSignature.parseRequest(ctx.req, { 'headers': [] });
+			signature = httpSignature.parseRequest(request.raw, { 'headers': [] });
 		} catch (e) {
-			ctx.status = 401;
+			reply.code(401);
 			return;
 		}
 
-		this.queueService.inbox(ctx.request.body, signature);
+		this.queueService.inbox(request.body, signature);
 
-		ctx.status = 202;
+		reply.code(202);
 	}
 
-	private async followers(ctx: Router.RouterContext) {
-		const userId = ctx.params.user;
+	private async followers(
+		request: FastifyRequest<{ Params: { user: string; }; Querystring: { cursor?: string; page?: string; }; }>,
+		reply: FastifyReply,
+	) {
+		const userId = request.params.user;
 
-		const cursor = ctx.request.query.cursor;
+		const cursor = request.query.cursor;
 		if (cursor != null && typeof cursor !== 'string') {
-			ctx.status = 400;
+			reply.code(400);
 			return;
 		}
 
-		const page = ctx.request.query.page === 'true';
+		const page = request.query.page === 'true';
 
 		const user = await this.usersRepository.findOneBy({
 			id: userId,
@@ -112,7 +117,7 @@ export class ActivityPubServerService {
 		});
 
 		if (user == null) {
-			ctx.status = 404;
+			reply.code(404);
 			return;
 		}
 
@@ -120,12 +125,12 @@ export class ActivityPubServerService {
 		const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id });
 
 		if (profile.ffVisibility === 'private') {
-			ctx.status = 403;
-			ctx.set('Cache-Control', 'public, max-age=30');
+			reply.code(403);
+			reply.header('Cache-Control', 'public, max-age=30');
 			return;
 		} else if (profile.ffVisibility === 'followers') {
-			ctx.status = 403;
-			ctx.set('Cache-Control', 'public, max-age=30');
+			reply.code(403);
+			reply.header('Cache-Control', 'public, max-age=30');
 			return;
 		}
 		//#endregion
@@ -168,27 +173,30 @@ export class ActivityPubServerService {
 				})}` : undefined,
 			);
 
-			ctx.body = this.apRendererService.renderActivity(rendered);
-			this.setResponseType(ctx);
+			this.setResponseType(request, reply);
+			return (this.apRendererService.renderActivity(rendered));
 		} else {
 			// index page
 			const rendered = this.apRendererService.renderOrderedCollection(partOf, user.followersCount, `${partOf}?page=true`);
-			ctx.body = this.apRendererService.renderActivity(rendered);
-			ctx.set('Cache-Control', 'public, max-age=180');
-			this.setResponseType(ctx);
+			reply.header('Cache-Control', 'public, max-age=180');
+			this.setResponseType(request, reply);
+			return (this.apRendererService.renderActivity(rendered));
 		}
 	}
 
-	private async following(ctx: Router.RouterContext) {
-		const userId = ctx.params.user;
+	private async following(
+		request: FastifyRequest<{ Params: { user: string; }; Querystring: { cursor?: string; page?: string; }; }>,
+		reply: FastifyReply,
+	) {
+		const userId = request.params.user;
 
-		const cursor = ctx.request.query.cursor;
+		const cursor = request.query.cursor;
 		if (cursor != null && typeof cursor !== 'string') {
-			ctx.status = 400;
+			reply.code(400);
 			return;
 		}
 	
-		const page = ctx.request.query.page === 'true';
+		const page = request.query.page === 'true';
 	
 		const user = await this.usersRepository.findOneBy({
 			id: userId,
@@ -196,7 +204,7 @@ export class ActivityPubServerService {
 		});
 	
 		if (user == null) {
-			ctx.status = 404;
+			reply.code(404);
 			return;
 		}
 	
@@ -204,12 +212,12 @@ export class ActivityPubServerService {
 		const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id });
 	
 		if (profile.ffVisibility === 'private') {
-			ctx.status = 403;
-			ctx.set('Cache-Control', 'public, max-age=30');
+			reply.code(403);
+			reply.header('Cache-Control', 'public, max-age=30');
 			return;
 		} else if (profile.ffVisibility === 'followers') {
-			ctx.status = 403;
-			ctx.set('Cache-Control', 'public, max-age=30');
+			reply.code(403);
+			reply.header('Cache-Control', 'public, max-age=30');
 			return;
 		}
 		//#endregion
@@ -252,19 +260,19 @@ export class ActivityPubServerService {
 				})}` : undefined,
 			);
 	
-			ctx.body = this.apRendererService.renderActivity(rendered);
-			this.setResponseType(ctx);
+			this.setResponseType(request, reply);
+			return (this.apRendererService.renderActivity(rendered));
 		} else {
 			// index page
 			const rendered = this.apRendererService.renderOrderedCollection(partOf, user.followingCount, `${partOf}?page=true`);
-			ctx.body = this.apRendererService.renderActivity(rendered);
-			ctx.set('Cache-Control', 'public, max-age=180');
-			this.setResponseType(ctx);
+			reply.header('Cache-Control', 'public, max-age=180');
+			this.setResponseType(request, reply);
+			return (this.apRendererService.renderActivity(rendered));
 		}
 	}
 
-	private async featured(ctx: Router.RouterContext) {
-		const userId = ctx.params.user;
+	private async featured(request: FastifyRequest<{ Params: { user: string; }; }>, reply: FastifyReply) {
+		const userId = request.params.user;
 
 		const user = await this.usersRepository.findOneBy({
 			id: userId,
@@ -272,7 +280,7 @@ export class ActivityPubServerService {
 		});
 
 		if (user == null) {
-			ctx.status = 404;
+			reply.code(404);
 			return;
 		}
 
@@ -291,30 +299,36 @@ export class ActivityPubServerService {
 			renderedNotes.length, undefined, undefined, renderedNotes,
 		);
 
-		ctx.body = this.apRendererService.renderActivity(rendered);
-		ctx.set('Cache-Control', 'public, max-age=180');
-		this.setResponseType(ctx);
+		reply.header('Cache-Control', 'public, max-age=180');
+		this.setResponseType(request, reply);
+		return (this.apRendererService.renderActivity(rendered));
 	}
 
-	private async outbox(ctx: Router.RouterContext) {
-		const userId = ctx.params.user;
+	private async outbox(
+		request: FastifyRequest<{
+			Params: { user: string; };
+			Querystring: { since_id?: string; until_id?: string; page?: string; };
+		}>,
+		reply: FastifyReply,
+	) {
+		const userId = request.params.user;
 
-		const sinceId = ctx.request.query.since_id;
+		const sinceId = request.query.since_id;
 		if (sinceId != null && typeof sinceId !== 'string') {
-			ctx.status = 400;
+			reply.code(400);
 			return;
 		}
 	
-		const untilId = ctx.request.query.until_id;
+		const untilId = request.query.until_id;
 		if (untilId != null && typeof untilId !== 'string') {
-			ctx.status = 400;
+			reply.code(400);
 			return;
 		}
 	
-		const page = ctx.request.query.page === 'true';
+		const page = request.query.page === 'true';
 	
 		if (countIf(x => x != null, [sinceId, untilId]) > 1) {
-			ctx.status = 400;
+			reply.code(400);
 			return;
 		}
 	
@@ -324,7 +338,7 @@ export class ActivityPubServerService {
 		});
 	
 		if (user == null) {
-			ctx.status = 404;
+			reply.code(404);
 			return;
 		}
 	
@@ -362,110 +376,130 @@ export class ActivityPubServerService {
 				})}` : undefined,
 			);
 	
-			ctx.body = this.apRendererService.renderActivity(rendered);
-			this.setResponseType(ctx);
+			this.setResponseType(request, reply);
+			return (this.apRendererService.renderActivity(rendered));
 		} else {
 			// index page
 			const rendered = this.apRendererService.renderOrderedCollection(partOf, user.notesCount,
 				`${partOf}?page=true`,
 				`${partOf}?page=true&since_id=000000000000000000000000`,
 			);
-			ctx.body = this.apRendererService.renderActivity(rendered);
-			ctx.set('Cache-Control', 'public, max-age=180');
-			this.setResponseType(ctx);
+			reply.header('Cache-Control', 'public, max-age=180');
+			this.setResponseType(request, reply);
+			return (this.apRendererService.renderActivity(rendered));
 		}
 	}
 
-	private async userInfo(ctx: Router.RouterContext, user: User | null) {
+	private async userInfo(request: FastifyRequest, reply: FastifyReply, user: User | null) {
 		if (user == null) {
-			ctx.status = 404;
+			reply.code(404);
 			return;
 		}
 
-		ctx.body = this.apRendererService.renderActivity(await this.apRendererService.renderPerson(user as ILocalUser));
-		ctx.set('Cache-Control', 'public, max-age=180');
-		this.setResponseType(ctx);
+		reply.header('Cache-Control', 'public, max-age=180');
+		this.setResponseType(request, reply);
+		return (this.apRendererService.renderActivity(await this.apRendererService.renderPerson(user as ILocalUser)));
 	}
 
-	public createRouter() {
-		// Init router
-		const router = new Router();
+	public createServer(fastify: FastifyInstance, options: FastifyPluginOptions, done: (err?: Error) => void) {
+		fastify.addConstraintStrategy({
+			name: 'apOrHtml',
+			storage() {
+				const store = {};
+				return {
+					get(key) {
+						return store[key] ?? null;
+					},
+					set(key, value) {
+						store[key] = value;
+					},
+				};
+			},
+			deriveConstraint(request, ctx) {
+				const accepted = accepts(request).type(['html', ACTIVITY_JSON, LD_JSON]);
+				const isAp = typeof accepted === 'string' && !accepted.match(/html/);
+				return isAp ? 'ap' : 'html';
+			},
+		});
+
+		fastify.register(fastifyAccepts);
 
 		//#region Routing
-		function isActivityPubReq(ctx: Router.RouterContext) {
-			ctx.response.vary('Accept');
-			const accepted = ctx.accepts('html', ACTIVITY_JSON, LD_JSON);
-			return typeof accepted === 'string' && !accepted.match(/html/);
-		}
-
 		// inbox
-		router.post('/inbox', json(), ctx => this.inbox(ctx));
-		router.post('/users/:user/inbox', json(), ctx => this.inbox(ctx));
+		fastify.post('/inbox', async (request, reply) => await this.inbox(request, reply));
+		fastify.post('/users/:user/inbox', async (request, reply) => await this.inbox(request, reply));
 
 		// note
-		router.get('/notes/:note', async (ctx, next) => {
-			if (!isActivityPubReq(ctx)) return await next();
-
+		fastify.get<{ Params: { note: string; } }>('/notes/:note', { constraints: { apOrHtml: 'ap' } }, async (request, reply) => {
 			const note = await this.notesRepository.findOneBy({
-				id: ctx.params.note,
+				id: request.params.note,
 				visibility: In(['public' as const, 'home' as const]),
 				localOnly: false,
 			});
 
 			if (note == null) {
-				ctx.status = 404;
+				reply.code(404);
 				return;
 			}
 
 			// リモートだったらリダイレクト
 			if (note.userHost != null) {
 				if (note.uri == null || this.utilityService.isSelfHost(note.userHost)) {
-					ctx.status = 500;
+					reply.code(500);
 					return;
 				}
-				ctx.redirect(note.uri);
+				reply.redirect(note.uri);
 				return;
 			}
 
-			ctx.body = this.apRendererService.renderActivity(await this.apRendererService.renderNote(note, false));
-			ctx.set('Cache-Control', 'public, max-age=180');
-			this.setResponseType(ctx);
+			reply.header('Cache-Control', 'public, max-age=180');
+			this.setResponseType(request, reply);
+			return (this.apRendererService.renderActivity(await this.apRendererService.renderNote(note, false)));
 		});
 
 		// note activity
-		router.get('/notes/:note/activity', async ctx => {
+		fastify.get<{ Params: { note: string; } }>('/notes/:note/activity', async (request, reply) => {
 			const note = await this.notesRepository.findOneBy({
-				id: ctx.params.note,
+				id: request.params.note,
 				userHost: IsNull(),
 				visibility: In(['public' as const, 'home' as const]),
 				localOnly: false,
 			});
 
 			if (note == null) {
-				ctx.status = 404;
+				reply.code(404);
 				return;
 			}
 
-			ctx.body = this.apRendererService.renderActivity(await this.packActivity(note));
-			ctx.set('Cache-Control', 'public, max-age=180');
-			this.setResponseType(ctx);
+			reply.header('Cache-Control', 'public, max-age=180');
+			this.setResponseType(request, reply);
+			return (this.apRendererService.renderActivity(await this.packActivity(note)));
 		});
 
 		// outbox
-		router.get('/users/:user/outbox', (ctx) => this.outbox(ctx));
+		fastify.get<{
+			Params: { user: string; };
+			Querystring: { since_id?: string; until_id?: string; page?: string; };
+		}>('/users/:user/outbox', async (request, reply) => await this.outbox(request, reply));
 
 		// followers
-		router.get('/users/:user/followers', (ctx) => this.followers(ctx));
+		fastify.get<{
+			Params: { user: string; };
+			Querystring: { cursor?: string; page?: string; };
+		}>('/users/:user/followers', async (request, reply) => await this.followers(request, reply));
 
 		// following
-		router.get('/users/:user/following', (ctx) => this.following(ctx));
+		fastify.get<{
+			Params: { user: string; };
+			Querystring: { cursor?: string; page?: string; };
+		}>('/users/:user/following', async (request, reply) => await this.following(request, reply));
 
 		// featured
-		router.get('/users/:user/collections/featured', (ctx) => this.featured(ctx));
+		fastify.get<{ Params: { user: string; }; }>('/users/:user/collections/featured', async (request, reply) => await this.featured(request, reply));
 
 		// publickey
-		router.get('/users/:user/publickey', async ctx => {
-			const userId = ctx.params.user;
+		fastify.get<{ Params: { user: string; } }>('/users/:user/publickey', async (request, reply) => {
+			const userId = request.params.user;
 
 			const user = await this.usersRepository.findOneBy({
 				id: userId,
@@ -473,25 +507,23 @@ export class ActivityPubServerService {
 			});
 
 			if (user == null) {
-				ctx.status = 404;
+				reply.code(404);
 				return;
 			}
 
 			const keypair = await this.userKeypairStoreService.getUserKeypair(user.id);
 
 			if (this.userEntityService.isLocalUser(user)) {
-				ctx.body = this.apRendererService.renderActivity(this.apRendererService.renderKey(user, keypair));
-				ctx.set('Cache-Control', 'public, max-age=180');
-				this.setResponseType(ctx);
+				reply.header('Cache-Control', 'public, max-age=180');
+				this.setResponseType(request, reply);
+				return (this.apRendererService.renderActivity(this.apRendererService.renderKey(user, keypair)));
 			} else {
-				ctx.status = 400;
+				reply.code(400);
 			}
 		});
 
-		router.get('/users/:user', async (ctx, next) => {
-			if (!isActivityPubReq(ctx)) return await next();
-
-			const userId = ctx.params.user;
+		fastify.get<{ Params: { user: string; } }>('/users/:user', { constraints: { apOrHtml: 'ap' } }, async (request, reply) => {
+			const userId = request.params.user;
 
 			const user = await this.usersRepository.findOneBy({
 				id: userId,
@@ -499,86 +531,84 @@ export class ActivityPubServerService {
 				isSuspended: false,
 			});
 
-			await this.userInfo(ctx, user);
+			return await this.userInfo(request, reply, user);
 		});
 
-		router.get('/@:user', async (ctx, next) => {
-			if (!isActivityPubReq(ctx)) return await next();
-
+		fastify.get<{ Params: { user: string; } }>('/@:user', { constraints: { apOrHtml: 'ap' } }, async (request, reply) => {
 			const user = await this.usersRepository.findOneBy({
-				usernameLower: ctx.params.user.toLowerCase(),
+				usernameLower: request.params.user.toLowerCase(),
 				host: IsNull(),
 				isSuspended: false,
 			});
 
-			await this.userInfo(ctx, user);
+			return await this.userInfo(request, reply, user);
 		});
 		//#endregion
 
 		// emoji
-		router.get('/emojis/:emoji', async ctx => {
+		fastify.get<{ Params: { emoji: string; } }>('/emojis/:emoji', async (request, reply) => {
 			const emoji = await this.emojisRepository.findOneBy({
 				host: IsNull(),
-				name: ctx.params.emoji,
+				name: request.params.emoji,
 			});
 
 			if (emoji == null) {
-				ctx.status = 404;
+				reply.code(404);
 				return;
 			}
 
-			ctx.body = this.apRendererService.renderActivity(await this.apRendererService.renderEmoji(emoji));
-			ctx.set('Cache-Control', 'public, max-age=180');
-			this.setResponseType(ctx);
+			reply.header('Cache-Control', 'public, max-age=180');
+			this.setResponseType(request, reply);
+			return (this.apRendererService.renderActivity(await this.apRendererService.renderEmoji(emoji)));
 		});
 
 		// like
-		router.get('/likes/:like', async ctx => {
-			const reaction = await this.noteReactionsRepository.findOneBy({ id: ctx.params.like });
+		fastify.get<{ Params: { like: string; } }>('/likes/:like', async (request, reply) => {
+			const reaction = await this.noteReactionsRepository.findOneBy({ id: request.params.like });
 
 			if (reaction == null) {
-				ctx.status = 404;
+				reply.code(404);
 				return;
 			}
 
 			const note = await this.notesRepository.findOneBy({ id: reaction.noteId });
 
 			if (note == null) {
-				ctx.status = 404;
+				reply.code(404);
 				return;
 			}
 
-			ctx.body = this.apRendererService.renderActivity(await this.apRendererService.renderLike(reaction, note));
-			ctx.set('Cache-Control', 'public, max-age=180');
-			this.setResponseType(ctx);
+			reply.header('Cache-Control', 'public, max-age=180');
+			this.setResponseType(request, reply);
+			return (this.apRendererService.renderActivity(await this.apRendererService.renderLike(reaction, note)));
 		});
 
 		// follow
-		router.get('/follows/:follower/:followee', async ctx => {
+		fastify.get<{ Params: { follower: string; followee: string; } }>('/follows/:follower/:followee', async (request, reply) => {
 			// This may be used before the follow is completed, so we do not
 			// check if the following exists.
 
 			const [follower, followee] = await Promise.all([
 				this.usersRepository.findOneBy({
-					id: ctx.params.follower,
+					id: request.params.follower,
 					host: IsNull(),
 				}),
 				this.usersRepository.findOneBy({
-					id: ctx.params.followee,
+					id: request.params.followee,
 					host: Not(IsNull()),
 				}),
 			]);
 
 			if (follower == null || followee == null) {
-				ctx.status = 404;
+				reply.code(404);
 				return;
 			}
 
-			ctx.body = this.apRendererService.renderActivity(this.apRendererService.renderFollow(follower, followee));
-			ctx.set('Cache-Control', 'public, max-age=180');
-			this.setResponseType(ctx);
+			reply.header('Cache-Control', 'public, max-age=180');
+			this.setResponseType(request, reply);
+			return (this.apRendererService.renderActivity(this.apRendererService.renderFollow(follower, followee)));
 		});
 
-		return router;
+		done();
 	}
 }
diff --git a/packages/backend/src/server/FileServerService.ts b/packages/backend/src/server/FileServerService.ts
index dc073e34ac..088e780d69 100644
--- a/packages/backend/src/server/FileServerService.ts
+++ b/packages/backend/src/server/FileServerService.ts
@@ -2,10 +2,8 @@ import * as fs from 'node:fs';
 import { fileURLToPath } from 'node:url';
 import { dirname } from 'node:path';
 import { Inject, Injectable } from '@nestjs/common';
-import Koa from 'koa';
-import cors from '@koa/cors';
-import Router from '@koa/router';
-import send from 'koa-send';
+import { FastifyInstance, FastifyRequest, FastifyReply, FastifyPluginOptions } from 'fastify';
+import fastifyStatic from '@fastify/static';
 import rename from 'rename';
 import type { Config } from '@/config.js';
 import type { DriveFilesRepository } from '@/models/index.js';
@@ -46,45 +44,44 @@ export class FileServerService {
 		private loggerService: LoggerService,
 	) {
 		this.logger = this.loggerService.getLogger('server', 'gray', false);
+
+		this.createServer = this.createServer.bind(this);
 	}
 
-	public commonReadableHandlerGenerator(ctx: Koa.Context) {
-		return (e: Error): void => {
-			this.logger.error(e);
-			ctx.status = 500;
-			ctx.set('Cache-Control', 'max-age=300');
+	public commonReadableHandlerGenerator(reply: FastifyReply) {
+		return (err: Error): void => {
+			this.logger.error(err);
+			reply.code(500);
+			reply.header('Cache-Control', 'max-age=300');
 		};
 	}
 	
-	public createServer() {
-		const app = new Koa();
-		app.use(cors());
-		app.use(async (ctx, next) => {
-			ctx.set('Content-Security-Policy', 'default-src \'none\'; img-src \'self\'; media-src \'self\'; style-src \'unsafe-inline\'');
-			await next();
+	public createServer(fastify: FastifyInstance, options: FastifyPluginOptions, done: (err?: Error) => void) {
+		fastify.addHook('onRequest', (request, reply, done) => {
+			reply.header('Content-Security-Policy', 'default-src \'none\'; img-src \'self\'; media-src \'self\'; style-src \'unsafe-inline\'');
+			done();
 		});
 
-		// Init router
-		const router = new Router();
+		fastify.register(fastifyStatic, {
+			root: _dirname,
+			serve: false,
+		});
 
-		router.get('/app-default.jpg', ctx => {
+		fastify.get('/app-default.jpg', (request, reply) => {
 			const file = fs.createReadStream(`${_dirname}/assets/dummy.png`);
-			ctx.body = file;
-			ctx.set('Content-Type', 'image/jpeg');
-			ctx.set('Cache-Control', 'max-age=31536000, immutable');
+			reply.header('Content-Type', 'image/jpeg');
+			reply.header('Cache-Control', 'max-age=31536000, immutable');
+			return reply.send(file);
 		});
 
-		router.get('/:key', ctx => this.sendDriveFile(ctx));
-		router.get('/:key/(.*)', ctx => this.sendDriveFile(ctx));
+		fastify.get<{ Params: { key: string; } }>('/:key', async (request, reply) => await this.sendDriveFile(request, reply));
+		fastify.get<{ Params: { key: string; } }>('/:key/*', async (request, reply) => await this.sendDriveFile(request, reply));
 
-		// Register router
-		app.use(router.routes());
-
-		return app;
+		done();
 	}
 
-	private async sendDriveFile(ctx: Koa.Context) {
-		const key = ctx.params.key;
+	private async sendDriveFile(request: FastifyRequest<{ Params: { key: string; } }>, reply: FastifyReply) {
+		const key = request.params.key;
 
 		// Fetch drive file
 		const file = await this.driveFilesRepository.createQueryBuilder('file')
@@ -94,10 +91,9 @@ export class FileServerService {
 			.getOne();
 
 		if (file == null) {
-			ctx.status = 404;
-			ctx.set('Cache-Control', 'max-age=86400');
-			await send(ctx as any, '/dummy.png', { root: assets });
-			return;
+			reply.code(404);
+			reply.header('Cache-Control', 'max-age=86400');
+			return reply.sendFile('/dummy.png', assets);
 		}
 
 		const isThumbnail = file.thumbnailAccessKey === key;
@@ -135,18 +131,18 @@ export class FileServerService {
 					};
 
 					const image = await convertFile();
-					ctx.body = image.data;
-					ctx.set('Content-Type', FILE_TYPE_BROWSERSAFE.includes(image.type) ? image.type : 'application/octet-stream');
-					ctx.set('Cache-Control', 'max-age=31536000, immutable');
+					reply.header('Content-Type', FILE_TYPE_BROWSERSAFE.includes(image.type) ? image.type : 'application/octet-stream');
+					reply.header('Cache-Control', 'max-age=31536000, immutable');
+					return image.data;
 				} catch (err) {
 					this.logger.error(`${err}`);
 
 					if (err instanceof StatusError && err.isClientError) {
-						ctx.status = err.statusCode;
-						ctx.set('Cache-Control', 'max-age=86400');
+						reply.code(err.statusCode);
+						reply.header('Cache-Control', 'max-age=86400');
 					} else {
-						ctx.status = 500;
-						ctx.set('Cache-Control', 'max-age=300');
+						reply.code(500);
+						reply.header('Cache-Control', 'max-age=300');
 					}
 				} finally {
 					cleanup();
@@ -154,8 +150,8 @@ export class FileServerService {
 				return;
 			}
 
-			ctx.status = 204;
-			ctx.set('Cache-Control', 'max-age=86400');
+			reply.code(204);
+			reply.header('Cache-Control', 'max-age=86400');
 			return;
 		}
 
@@ -166,18 +162,17 @@ export class FileServerService {
 				extname: ext ? `.${ext}` : undefined,
 			}).toString();
 
-			ctx.body = this.internalStorageService.read(key);
-			ctx.set('Content-Type', FILE_TYPE_BROWSERSAFE.includes(mime) ? mime : 'application/octet-stream');
-			ctx.set('Cache-Control', 'max-age=31536000, immutable');
-			ctx.set('Content-Disposition', contentDisposition('inline', filename));
+			reply.header('Content-Type', FILE_TYPE_BROWSERSAFE.includes(mime) ? mime : 'application/octet-stream');
+			reply.header('Cache-Control', 'max-age=31536000, immutable');
+			reply.header('Content-Disposition', contentDisposition('inline', filename));
+			return this.internalStorageService.read(key);
 		} else {
 			const readable = this.internalStorageService.read(file.accessKey!);
-			readable.on('error', this.commonReadableHandlerGenerator(ctx));
-			ctx.body = readable;
-			ctx.set('Content-Type', FILE_TYPE_BROWSERSAFE.includes(file.type) ? file.type : 'application/octet-stream');
-			ctx.set('Cache-Control', 'max-age=31536000, immutable');
-			ctx.set('Content-Disposition', contentDisposition('inline', file.name));
+			readable.on('error', this.commonReadableHandlerGenerator(reply));
+			reply.header('Content-Type', FILE_TYPE_BROWSERSAFE.includes(file.type) ? file.type : 'application/octet-stream');
+			reply.header('Cache-Control', 'max-age=31536000, immutable');
+			reply.header('Content-Disposition', contentDisposition('inline', file.name));
+			return readable;
 		}
 	}
 }
-
diff --git a/packages/backend/src/server/MediaProxyServerService.ts b/packages/backend/src/server/MediaProxyServerService.ts
index 31841d39df..4d7bbdf599 100644
--- a/packages/backend/src/server/MediaProxyServerService.ts
+++ b/packages/backend/src/server/MediaProxyServerService.ts
@@ -1,8 +1,6 @@
 import * as fs from 'node:fs';
 import { Inject, Injectable } from '@nestjs/common';
-import Koa from 'koa';
-import cors from '@koa/cors';
-import Router from '@koa/router';
+import { FastifyInstance, FastifyPluginOptions, FastifyReply, FastifyRequest } from 'fastify';
 import sharp from 'sharp';
 import { DI } from '@/di-symbols.js';
 import type { Config } from '@/config.js';
@@ -31,32 +29,29 @@ export class MediaProxyServerService {
 		private loggerService: LoggerService,
 	) {
 		this.logger = this.loggerService.getLogger('server', 'gray', false);
+
+		this.createServer = this.createServer.bind(this);
 	}
 
-	public createServer() {
-		const app = new Koa();
-		app.use(cors());
-		app.use(async (ctx, next) => {
-			ctx.set('Content-Security-Policy', 'default-src \'none\'; img-src \'self\'; media-src \'self\'; style-src \'unsafe-inline\'');
-			await next();
+	public createServer(fastify: FastifyInstance, options: FastifyPluginOptions, done: (err?: Error) => void) {
+		fastify.addHook('onRequest', (request, reply, done) => {
+			reply.header('Content-Security-Policy', 'default-src \'none\'; img-src \'self\'; media-src \'self\'; style-src \'unsafe-inline\'');
+			done();
 		});
 
-		// Init router
-		const router = new Router();
+		fastify.get<{
+			Params: { url: string; };
+			Querystring: { url?: string; };
+		}>('/:url*', async (request, reply) => await this.handler(request, reply));
 
-		router.get('/:url*', ctx => this.handler(ctx));
-
-		// Register router
-		app.use(router.routes());
-
-		return app;
+		done();
 	}
 
-	private async handler(ctx: Koa.Context) {
-		const url = 'url' in ctx.query ? ctx.query.url : 'https://' + ctx.params.url;
+	private async handler(request: FastifyRequest<{ Params: { url: string; }; Querystring: { url?: string; }; }>, reply: FastifyReply) {
+		const url = 'url' in request.query ? request.query.url : 'https://' + request.params.url;
 	
 		if (typeof url !== 'string') {
-			ctx.status = 400;
+			reply.code(400);
 			return;
 		}
 	
@@ -71,11 +66,11 @@ export class MediaProxyServerService {
 	
 			let image: IImage;
 	
-			if ('static' in ctx.query && isConvertibleImage) {
+			if ('static' in request.query && isConvertibleImage) {
 				image = await this.imageProcessingService.convertToWebp(path, 498, 280);
-			} else if ('preview' in ctx.query && isConvertibleImage) {
+			} else if ('preview' in request.query && isConvertibleImage) {
 				image = await this.imageProcessingService.convertToWebp(path, 200, 200);
-			} else if ('badge' in ctx.query) {
+			} else if ('badge' in request.query) {
 				if (!isConvertibleImage) {
 					// 画像でないなら404でお茶を濁す
 					throw new StatusError('Unexpected mime', 404);
@@ -122,16 +117,16 @@ export class MediaProxyServerService {
 				};
 			}
 	
-			ctx.set('Content-Type', image.type);
-			ctx.set('Cache-Control', 'max-age=31536000, immutable');
-			ctx.body = image.data;
+			reply.header('Content-Type', image.type);
+			reply.header('Cache-Control', 'max-age=31536000, immutable');
+			return image.data;
 		} catch (err) {
 			this.logger.error(`${err}`);
 	
 			if (err instanceof StatusError && (err.statusCode === 302 || err.isClientError)) {
-				ctx.status = err.statusCode;
+				reply.code(err.statusCode);
 			} else {
-				ctx.status = 500;
+				reply.code(500);
 			}
 		} finally {
 			cleanup();
diff --git a/packages/backend/src/server/NodeinfoServerService.ts b/packages/backend/src/server/NodeinfoServerService.ts
index ef4ec74a35..b85925f53e 100644
--- a/packages/backend/src/server/NodeinfoServerService.ts
+++ b/packages/backend/src/server/NodeinfoServerService.ts
@@ -1,5 +1,5 @@
 import { Inject, Injectable } from '@nestjs/common';
-import Router from '@koa/router';
+import { FastifyInstance, FastifyPluginOptions, FastifyReply, FastifyRequest } from 'fastify';
 import { IsNull, MoreThan } from 'typeorm';
 import { DI } from '@/di-symbols.js';
 import type { NotesRepository, UsersRepository } from '@/models/index.js';
@@ -27,6 +27,7 @@ export class NodeinfoServerService {
 		private userEntityService: UserEntityService,
 		private metaService: MetaService,
 	) {
+		this.createServer = this.createServer.bind(this);
 	}
 
 	public getLinks() {
@@ -39,9 +40,7 @@ export class NodeinfoServerService {
 			}];
 	}
 
-	public createRouter() {
-		const router = new Router();
-
+	public createServer(fastify: FastifyInstance, options: FastifyPluginOptions, done: (err?: Error) => void) {
 		const nodeinfo2 = async () => {
 			const now = Date.now();
 			const [
@@ -108,22 +107,22 @@ export class NodeinfoServerService {
 
 		const cache = new Cache<Awaited<ReturnType<typeof nodeinfo2>>>(1000 * 60 * 10);
 
-		router.get(nodeinfo2_1path, async ctx => {
+		fastify.get(nodeinfo2_1path, async (request, reply) => {
 			const base = await cache.fetch(null, () => nodeinfo2());
 
-			ctx.body = { version: '2.1', ...base };
-			ctx.set('Cache-Control', 'public, max-age=600');
+			reply.header('Cache-Control', 'public, max-age=600');
+			return { version: '2.1', ...base };
 		});
 
-		router.get(nodeinfo2_0path, async ctx => {
+		fastify.get(nodeinfo2_0path, async (request, reply) => {
 			const base = await cache.fetch(null, () => nodeinfo2());
 
 			delete (base as any).software.repository;
 
-			ctx.body = { version: '2.0', ...base };
-			ctx.set('Cache-Control', 'public, max-age=600');
+			reply.header('Cache-Control', 'public, max-age=600');
+			return { version: '2.0', ...base };
 		});
 
-		return router;
+		done();
 	}
 }
diff --git a/packages/backend/src/server/ServerService.ts b/packages/backend/src/server/ServerService.ts
index d42972614f..96159cfc53 100644
--- a/packages/backend/src/server/ServerService.ts
+++ b/packages/backend/src/server/ServerService.ts
@@ -2,11 +2,7 @@ import cluster from 'node:cluster';
 import * as fs from 'node:fs';
 import * as http from 'node:http';
 import { Inject, Injectable } from '@nestjs/common';
-import Koa from 'koa';
-import Router from '@koa/router';
-import mount from 'koa-mount';
-import koaLogger from 'koa-logger';
-import * as slow from 'koa-slow';
+import Fastify from 'fastify';
 import { IsNull } from 'typeorm';
 import { GlobalEventService } from '@/core/GlobalEventService.js';
 import type { Config } from '@/config.js';
@@ -58,47 +54,29 @@ export class ServerService {
 	}
 
 	public launch() {
-		// Init app
-		const koa = new Koa();
-		koa.proxy = true;
-
-		if (!['production', 'test'].includes(process.env.NODE_ENV ?? '')) {
-		// Logger
-			koa.use(koaLogger(str => {
-				this.logger.info(str);
-			}));
-
-			// Delay
-			if (envOption.slow) {
-				koa.use(slow({
-					delay: 3000,
-				}));
-			}
-		}
+		const fastify = Fastify({
+			trustProxy: true,
+			logger: !['production', 'test'].includes(process.env.NODE_ENV ?? ''),
+		});
 
 		// HSTS
 		// 6months (15552000sec)
 		if (this.config.url.startsWith('https') && !this.config.disableHsts) {
-			koa.use(async (ctx, next) => {
-				ctx.set('strict-transport-security', 'max-age=15552000; preload');
-				await next();
+			fastify.addHook('onRequest', (request, reply, done) => {
+				reply.header('strict-transport-security', 'max-age=15552000; preload');
+				done();
 			});
 		}
 
-		koa.use(mount('/api', this.apiServerService.createApiServer(koa)));
-		koa.use(mount('/files', this.fileServerService.createServer()));
-		koa.use(mount('/proxy', this.mediaProxyServerService.createServer()));
+		fastify.register(this.apiServerService.createServer, { prefix: '/api' });
+		fastify.register(this.fileServerService.createServer, { prefix: '/files' });
+		fastify.register(this.mediaProxyServerService.createServer, { prefix: '/proxy' });
+		fastify.register(this.activityPubServerService.createServer);
+		fastify.register(this.nodeinfoServerService.createServer);
+		fastify.register(this.wellKnownServerService.createServer);
 
-		// Init router
-		const router = new Router();
-
-		// Routing
-		router.use(this.activityPubServerService.createRouter().routes());
-		router.use(this.nodeinfoServerService.createRouter().routes());
-		router.use(this.wellKnownServerService.createRouter().routes());
-
-		router.get('/avatar/@:acct', async ctx => {
-			const { username, host } = Acct.parse(ctx.params.acct);
+		fastify.get<{ Params: { acct: string } }>('/avatar/@:acct', async (request, reply) => {
+			const { username, host } = Acct.parse(request.params.acct);
 			const user = await this.usersRepository.findOne({
 				where: {
 					usernameLower: username.toLowerCase(),
@@ -109,28 +87,25 @@ export class ServerService {
 			});
 
 			if (user) {
-				ctx.redirect(this.userEntityService.getAvatarUrlSync(user));
+				reply.redirect(this.userEntityService.getAvatarUrlSync(user));
 			} else {
-				ctx.redirect('/static-assets/user-unknown.png');
+				reply.redirect('/static-assets/user-unknown.png');
 			}
 		});
 
-		router.get('/identicon/:x', async ctx => {
+		fastify.get<{ Params: { x: string } }>('/identicon/:x', async (request, reply) => {
 			const [temp, cleanup] = await createTemp();
-			await genIdenticon(ctx.params.x, fs.createWriteStream(temp));
-			ctx.set('Content-Type', 'image/png');
-			ctx.body = fs.createReadStream(temp).on('close', () => cleanup());
+			await genIdenticon(request.params.x, fs.createWriteStream(temp));
+			reply.header('Content-Type', 'image/png');
+			return fs.createReadStream(temp).on('close', () => cleanup());
 		});
 
-		router.get('/verify-email/:code', async ctx => {
+		fastify.get<{ Params: { code: string } }>('/verify-email/:code', async (request, reply) => {
 			const profile = await this.userProfilesRepository.findOneBy({
-				emailVerifyCode: ctx.params.code,
+				emailVerifyCode: request.params.code,
 			});
 
 			if (profile != null) {
-				ctx.body = 'Verify succeeded!';
-				ctx.status = 200;
-
 				await this.userProfilesRepository.update({ userId: profile.userId }, {
 					emailVerified: true,
 					emailVerifyCode: null,
@@ -140,21 +115,19 @@ export class ServerService {
 					detail: true,
 					includeSecrets: true,
 				}));
+
+				reply.code(200);
+				return 'Verify succeeded!';
 			} else {
-				ctx.status = 404;
+				reply.code(404);
 			}
 		});
 
-		// Register router
-		koa.use(router.routes());
+		fastify.register(this.clientServerService.createServer);
 
-		koa.use(mount(this.clientServerService.createApp()));
+		this.streamingApiServerService.attachStreamingApi(fastify.server);
 
-		const server = http.createServer(koa.callback());
-
-		this.streamingApiServerService.attachStreamingApi(server);
-
-		server.on('error', err => {
+		fastify.server.on('error', err => {
 			switch ((err as any).code) {
 				case 'EACCES':
 					this.logger.error(`You do not have permission to listen on port ${this.config.port}.`);
@@ -168,13 +141,13 @@ export class ServerService {
 			}
 
 			if (cluster.isWorker) {
-			process.send!('listenFailed');
+				process.send!('listenFailed');
 			} else {
-			// disableClustering
+				// disableClustering
 				process.exit(1);
 			}
 		});
 
-		server.listen(this.config.port);
+		fastify.listen({ port: this.config.port });
 	}
 }
diff --git a/packages/backend/src/server/WellKnownServerService.ts b/packages/backend/src/server/WellKnownServerService.ts
index f2eee88e09..412c608313 100644
--- a/packages/backend/src/server/WellKnownServerService.ts
+++ b/packages/backend/src/server/WellKnownServerService.ts
@@ -1,6 +1,7 @@
 import { Inject, Injectable } from '@nestjs/common';
-import Router from '@koa/router';
+import { FastifyInstance, FastifyPluginOptions, FastifyReply, FastifyRequest } from 'fastify';
 import { IsNull, MoreThan } from 'typeorm';
+import vary from 'vary';
 import { DI } from '@/di-symbols.js';
 import type { UsersRepository } from '@/models/index.js';
 import type { Config } from '@/config.js';
@@ -21,11 +22,10 @@ export class WellKnownServerService {
 
 		private nodeinfoServerService: NodeinfoServerService,
 	) {
+		this.createServer = this.createServer.bind(this);
 	}
 
-	public createRouter() {
-		const router = new Router();
-
+	public createServer(fastify: FastifyInstance, options: FastifyPluginOptions, done: (err?: Error) => void) {
 		const XRD = (...x: { element: string, value?: string, attributes?: Record<string, string> }[]) =>
 			`<?xml version="1.0" encoding="UTF-8"?><XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0">${x.map(({ element, value, attributes }) =>
 				`<${
@@ -34,37 +34,35 @@ export class WellKnownServerService {
 					typeof value === 'string' ? `>${escapeValue(value)}</${element}` : '/'
 				}>`).reduce((a, c) => a + c, '')}</XRD>`;
 
-		const allPath = '/.well-known/(.*)';
+		const allPath = '/.well-known/*';
 		const webFingerPath = '/.well-known/webfinger';
 		const jrd = 'application/jrd+json';
 		const xrd = 'application/xrd+xml';
 
-		router.use(allPath, async (ctx, next) => {
-			ctx.set({
-				'Access-Control-Allow-Headers': 'Accept',
-				'Access-Control-Allow-Methods': 'GET, OPTIONS',
-				'Access-Control-Allow-Origin': '*',
-				'Access-Control-Expose-Headers': 'Vary',
-			});
-			await next();
+		fastify.addHook('onRequest', (request, reply, done) => {
+			reply.header('Access-Control-Allow-Headers', 'Accept');
+			reply.header('Access-Control-Allow-Methods', 'GET, OPTIONS');
+			reply.header('Access-Control-Allow-Origin', '*');
+			reply.header('Access-Control-Expose-Headers', 'Vary');
+			done();
 		});
 
-		router.options(allPath, async ctx => {
-			ctx.status = 204;
+		fastify.options(allPath, async (request, reply) => {
+			reply.code(204);
 		});
 
-		router.get('/.well-known/host-meta', async ctx => {
-			ctx.set('Content-Type', xrd);
-			ctx.body = XRD({ element: 'Link', attributes: {
+		fastify.get('/.well-known/host-meta', async (request, reply) => {
+			reply.header('Content-Type', xrd);
+			return XRD({ element: 'Link', attributes: {
 				rel: 'lrdd',
 				type: xrd,
 				template: `${this.config.url}${webFingerPath}?resource={uri}`,
 			} });
 		});
 
-		router.get('/.well-known/host-meta.json', async ctx => {
-			ctx.set('Content-Type', jrd);
-			ctx.body = {
+		fastify.get('/.well-known/host-meta.json', async (request, reply) => {
+			reply.header('Content-Type', jrd);
+			return {
 				links: [{
 					rel: 'lrdd',
 					type: jrd,
@@ -73,16 +71,16 @@ export class WellKnownServerService {
 			};
 		});
 
-		router.get('/.well-known/nodeinfo', async ctx => {
-			ctx.body = { links: this.nodeinfoServerService.getLinks() };
+		fastify.get('/.well-known/nodeinfo', async (request, reply) => {
+			return { links: this.nodeinfoServerService.getLinks() };
 		});
 
 		/* TODO
-router.get('/.well-known/change-password', async ctx => {
+fastify.get('/.well-known/change-password', async (request, reply) => {
 });
 */
 
-		router.get(webFingerPath, async ctx => {
+		fastify.get<{ Querystring: { resource: string } }>(webFingerPath, async (request, reply) => {
 			const fromId = (id: User['id']): FindOptionsWhere<User> => ({
 				id,
 				host: IsNull(),
@@ -104,22 +102,22 @@ router.get('/.well-known/change-password', async ctx => {
 					isSuspended: false,
 				} : 422;
 
-			if (typeof ctx.query.resource !== 'string') {
-				ctx.status = 400;
+			if (typeof request.query.resource !== 'string') {
+				reply.code(400);
 				return;
 			}
 
-			const query = generateQuery(ctx.query.resource.toLowerCase());
+			const query = generateQuery(request.query.resource.toLowerCase());
 
 			if (typeof query === 'number') {
-				ctx.status = query;
+				reply.code(query);
 				return;
 			}
 
 			const user = await this.usersRepository.findOneBy(query);
 
 			if (user == null) {
-				ctx.status = 404;
+				reply.code(404);
 				return;
 			}
 
@@ -139,30 +137,25 @@ router.get('/.well-known/change-password', async ctx => {
 				template: `${this.config.url}/authorize-follow?acct={uri}`,
 			};
 
-			if (ctx.accepts(jrd, xrd) === xrd) {
-				ctx.body = XRD(
+			vary(reply.raw, 'Accept');
+			reply.header('Cache-Control', 'public, max-age=180');
+
+			if (request.accepts().type([jrd, xrd]) === xrd) {
+				reply.type(xrd);
+				return XRD(
 					{ element: 'Subject', value: subject },
 					{ element: 'Link', attributes: self },
 					{ element: 'Link', attributes: profilePage },
 					{ element: 'Link', attributes: subscribe });
-				ctx.type = xrd;
 			} else {
-				ctx.body = {
+				reply.type(jrd);
+				return {
 					subject,
 					links: [self, profilePage, subscribe],
 				};
-				ctx.type = jrd;
 			}
-
-			ctx.vary('Accept');
-			ctx.set('Cache-Control', 'public, max-age=180');
 		});
 
-		// Return 404 for other .well-known
-		router.all(allPath, async ctx => {
-			ctx.status = 404;
-		});
-
-		return router;
+		done();
 	}
 }
diff --git a/packages/backend/src/server/api/ApiCallService.ts b/packages/backend/src/server/api/ApiCallService.ts
index c3ce12e0c3..2e72cdf9f8 100644
--- a/packages/backend/src/server/api/ApiCallService.ts
+++ b/packages/backend/src/server/api/ApiCallService.ts
@@ -1,19 +1,25 @@
 import { performance } from 'perf_hooks';
+import { pipeline } from 'node:stream';
+import * as fs from 'node:fs';
+import { promisify } from 'node:util';
 import { Inject, Injectable } from '@nestjs/common';
+import { FastifyRequest, FastifyReply } from 'fastify';
 import { DI } from '@/di-symbols.js';
 import { getIpHash } from '@/misc/get-ip-hash.js';
-import type { CacheableLocalUser, User } from '@/models/entities/User.js';
+import type { CacheableLocalUser, ILocalUser, User } from '@/models/entities/User.js';
 import type { AccessToken } from '@/models/entities/AccessToken.js';
 import type Logger from '@/logger.js';
 import type { UserIpsRepository } from '@/models/index.js';
 import { MetaService } from '@/core/MetaService.js';
+import { createTemp } from '@/misc/create-temp.js';
 import { ApiError } from './error.js';
 import { RateLimiterService } from './RateLimiterService.js';
 import { ApiLoggerService } from './ApiLoggerService.js';
 import { AuthenticateService, AuthenticationError } from './AuthenticateService.js';
 import type { OnApplicationShutdown } from '@nestjs/common';
 import type { IEndpointMeta, IEndpoint } from './endpoints.js';
-import type Koa from 'koa';
+
+const pump = promisify(pipeline);
 
 const accessDenied = {
 	message: 'Access denied.',
@@ -44,92 +50,149 @@ export class ApiCallService implements OnApplicationShutdown {
 		}, 1000 * 60 * 60);
 	}
 
-	public handleRequest(endpoint: IEndpoint, exec: any, ctx: Koa.Context) {
-		return new Promise<void>((res) => {
-			const body = ctx.is('multipart/form-data')
-				? (ctx.request as any).body
-				: ctx.method === 'GET'
-					? ctx.query
-					: ctx.request.body;
-		
-			const reply = (x?: any, y?: ApiError) => {
-				if (x == null) {
-					ctx.status = 204;
-				} else if (typeof x === 'number' && y) {
-					ctx.status = x;
-					ctx.body = {
-						error: {
-							message: y!.message,
-							code: y!.code,
-							id: y!.id,
-							kind: y!.kind,
-							...(y!.info ? { info: y!.info } : {}),
-						},
-					};
-				} else {
-					// 文字列を返す場合は、JSON.stringify通さないとJSONと認識されない
-					ctx.body = typeof x === 'string' ? JSON.stringify(x) : x;
-				}
-				res();
-			};
-		
-			// Authentication
-			this.authenticateService.authenticate(body['i']).then(([user, app]) => {
-				// API invoking
-				this.call(endpoint, exec, user, app, body, ctx).then((res: any) => {
-					if (ctx.method === 'GET' && endpoint.meta.cacheSec && !body['i'] && !user) {
-						ctx.set('Cache-Control', `public, max-age=${endpoint.meta.cacheSec}`);
-					}
-					reply(res);
-				}).catch((e: ApiError) => {
-					reply(e.httpStatusCode ? e.httpStatusCode : e.kind === 'client' ? 400 : 500, e);
-				});
-		
-				// Log IP
-				if (user) {
-					this.metaService.fetch().then(meta => {
-						if (!meta.enableIpLogging) return;
-						const ip = ctx.ip;
-						const ips = this.userIpHistories.get(user.id);
-						if (ips == null || !ips.has(ip)) {
-							if (ips == null) {
-								this.userIpHistories.set(user.id, new Set([ip]));
-							} else {
-								ips.add(ip);
-							}
-		
-							try {
-								this.userIpsRepository.createQueryBuilder().insert().values({
-									createdAt: new Date(),
-									userId: user.id,
-									ip: ip,
-								}).orIgnore(true).execute();
-							} catch {
-							}
-						}
-					});
-				}
-			}).catch(e => {
-				if (e instanceof AuthenticationError) {
-					reply(403, new ApiError({
-						message: 'Authentication failed. Please ensure your token is correct.',
-						code: 'AUTHENTICATION_FAILED',
-						id: 'b0a7f5f8-dc2f-4171-b91f-de88ad238e14',
-					}));
-				} else {
-					reply(500, new ApiError());
+	public handleRequest(
+		endpoint: IEndpoint & { exec: any },
+		request: FastifyRequest<{ Body: Record<string, unknown>, Querystring: Record<string, unknown> }>,
+		reply: FastifyReply,
+	) {
+		const body = request.method === 'GET'
+			? request.query
+			: request.body;
+
+		const token = body['i'];
+		if (token != null && typeof token !== 'string') {
+			reply.code(400);
+			return;
+		}
+		this.authenticateService.authenticate(token).then(([user, app]) => {
+			this.call(endpoint, user, app, body, null, request).then((res) => {
+				if (request.method === 'GET' && endpoint.meta.cacheSec && !body['i'] && !user) {
+					reply.header('Cache-Control', `public, max-age=${endpoint.meta.cacheSec}`);
 				}
+				this.send(reply, res);
+			}).catch((err: ApiError) => {
+				this.send(reply, err.httpStatusCode ? err.httpStatusCode : err.kind === 'client' ? 400 : 500, err);
 			});
+
+			if (user) {
+				this.logIp(request, user);
+			}
+		}).catch(err => {
+			if (err instanceof AuthenticationError) {
+				this.send(reply, 403, new ApiError({
+					message: 'Authentication failed. Please ensure your token is correct.',
+					code: 'AUTHENTICATION_FAILED',
+					id: 'b0a7f5f8-dc2f-4171-b91f-de88ad238e14',
+				}));
+			} else {
+				this.send(reply, 500, new ApiError());
+			}
 		});
 	}
 
+	public async handleMultipartRequest(
+		endpoint: IEndpoint & { exec: any },
+		request: FastifyRequest<{ Body: Record<string, unknown>, Querystring: Record<string, unknown> }>,
+		reply: FastifyReply,
+	) {
+		const multipartData = await request.file();
+		if (multipartData == null) {
+			reply.code(400);
+			return;
+		}
+
+		const [path] = await createTemp();
+		await pump(multipartData.file, fs.createWriteStream(path));
+
+		const fields = {} as Record<string, string | undefined>;
+		for (const [k, v] of Object.entries(multipartData.fields)) {
+			fields[k] = v.value;
+		}
+	
+		const token = fields['i'];
+		if (token != null && typeof token !== 'string') {
+			reply.code(400);
+			return;
+		}
+		this.authenticateService.authenticate(token).then(([user, app]) => {
+			this.call(endpoint, user, app, fields, {
+				name: multipartData.filename,
+				path: path,
+			}, request).then((res) => {
+				this.send(reply, res);
+			}).catch((err: ApiError) => {
+				this.send(reply, err.httpStatusCode ? err.httpStatusCode : err.kind === 'client' ? 400 : 500, err);
+			});
+
+			if (user) {
+				this.logIp(request, user);
+			}
+		}).catch(err => {
+			if (err instanceof AuthenticationError) {
+				this.send(reply, 403, new ApiError({
+					message: 'Authentication failed. Please ensure your token is correct.',
+					code: 'AUTHENTICATION_FAILED',
+					id: 'b0a7f5f8-dc2f-4171-b91f-de88ad238e14',
+				}));
+			} else {
+				this.send(reply, 500, new ApiError());
+			}
+		});
+	}
+
+	private send(reply: FastifyReply, x?: any, y?: ApiError) {
+		if (x == null) {
+			reply.code(204);
+		} else if (typeof x === 'number' && y) {
+			reply.code(x);
+			reply.send({
+				error: {
+					message: y!.message,
+					code: y!.code,
+					id: y!.id,
+					kind: y!.kind,
+					...(y!.info ? { info: y!.info } : {}),
+				},
+			});
+		} else {
+			// 文字列を返す場合は、JSON.stringify通さないとJSONと認識されない
+			reply.send(typeof x === 'string' ? JSON.stringify(x) : x);
+		}
+	}
+
+	private async logIp(request: FastifyRequest, user: ILocalUser) {
+		const meta = await this.metaService.fetch();
+		if (!meta.enableIpLogging) return;
+		const ip = request.ip;
+		const ips = this.userIpHistories.get(user.id);
+		if (ips == null || !ips.has(ip)) {
+			if (ips == null) {
+				this.userIpHistories.set(user.id, new Set([ip]));
+			} else {
+				ips.add(ip);
+			}
+
+			try {
+				this.userIpsRepository.createQueryBuilder().insert().values({
+					createdAt: new Date(),
+					userId: user.id,
+					ip: ip,
+				}).orIgnore(true).execute();
+			} catch {
+			}
+		}
+	}
+
 	private async call(
-		ep: IEndpoint,
-		exec: any,
+		ep: IEndpoint & { exec: any },
 		user: CacheableLocalUser | null | undefined,
 		token: AccessToken | null | undefined,
 		data: any,
-		ctx?: Koa.Context,
+		file: {
+			name: string;
+			path: string;
+		} | null,
+		request: FastifyRequest<{ Body: Record<string, unknown>, Querystring: Record<string, unknown> }>,
 	) {
 		const isSecure = user != null && token == null;
 		const isModerator = user != null && (user.isModerator || user.isAdmin);
@@ -144,7 +207,7 @@ export class ApiCallService implements OnApplicationShutdown {
 			if (user) {
 				limitActor = user.id;
 			} else {
-				limitActor = getIpHash(ctx!.ip);
+				limitActor = getIpHash(request.ip);
 			}
 
 			const limit = Object.assign({}, ep.meta.limit);
@@ -154,7 +217,7 @@ export class ApiCallService implements OnApplicationShutdown {
 			}
 
 			// Rate limit
-			await this.rateLimiterService.limit(limit as IEndpointMeta['limit'] & { key: NonNullable<string> }, limitActor).catch(e => {
+			await this.rateLimiterService.limit(limit as IEndpointMeta['limit'] & { key: NonNullable<string> }, limitActor).catch(err => {
 				throw new ApiError({
 					message: 'Rate limit exceeded. Please try again later.',
 					code: 'RATE_LIMIT_EXCEEDED',
@@ -199,7 +262,7 @@ export class ApiCallService implements OnApplicationShutdown {
 		}
 
 		// Cast non JSON input
-		if ((ep.meta.requireFile || ctx?.method === 'GET') && ep.params.properties) {
+		if ((ep.meta.requireFile || request.method === 'GET') && ep.params.properties) {
 			for (const k of Object.keys(ep.params.properties)) {
 				const param = ep.params.properties![k];
 				if (['boolean', 'number', 'integer'].includes(param.type ?? '') && typeof data[k] === 'string') {
@@ -221,7 +284,7 @@ export class ApiCallService implements OnApplicationShutdown {
 
 		// API invoking
 		const before = performance.now();
-		return await exec(data, user, token, ctx?.file, ctx?.ip, ctx?.headers).catch((err: Error) => {
+		return await ep.exec(data, user, token, file, request.ip, request.headers).catch((err: Error) => {
 			if (err instanceof ApiError) {
 				throw err;
 			} else {
diff --git a/packages/backend/src/server/api/ApiServerService.ts b/packages/backend/src/server/api/ApiServerService.ts
index 52654dbaee..cf3f2deebf 100644
--- a/packages/backend/src/server/api/ApiServerService.ts
+++ b/packages/backend/src/server/api/ApiServerService.ts
@@ -1,15 +1,13 @@
 import { Inject, Injectable } from '@nestjs/common';
-import Koa from 'koa';
-import Router from '@koa/router';
-import multer from '@koa/multer';
-import bodyParser from 'koa-bodyparser';
-import cors from '@koa/cors';
-import { ModuleRef } from '@nestjs/core';
+import { FastifyInstance, FastifyPluginOptions } from 'fastify';
+import cors from '@fastify/cors';
+import multipart from '@fastify/multipart';
+import { ModuleRef, repl } from '@nestjs/core';
 import type { Config } from '@/config.js';
 import type { UsersRepository, InstancesRepository, AccessTokensRepository } from '@/models/index.js';
 import { DI } from '@/di-symbols.js';
 import { UserEntityService } from '@/core/entities/UserEntityService.js';
-import endpoints from './endpoints.js';
+import endpoints, { IEndpoint } from './endpoints.js';
 import { ApiCallService } from './ApiCallService.js';
 import { SignupApiService } from './SignupApiService.js';
 import { SigninApiService } from './SigninApiService.js';
@@ -42,92 +40,107 @@ export class ApiServerService {
 		private discordServerService: DiscordServerService,
 		private twitterServerService: TwitterServerService,
 	) {
+		this.createServer = this.createServer.bind(this);
 	}
 
-	public createApiServer() {
-		const handlers: Record<string, any> = {};
-
-		for (const endpoint of endpoints) {
-			handlers[endpoint.name] = this.moduleRef.get('ep:' + endpoint.name, { strict: false }).exec;
-		}
-
-		// Init app
-		const apiServer = new Koa();
-
-		apiServer.use(cors({
+	public createServer(fastify: FastifyInstance, options: FastifyPluginOptions, done: (err?: Error) => void) {
+		fastify.register(cors, {
 			origin: '*',
-		}));
-
-		// No caching
-		apiServer.use(async (ctx, next) => {
-			ctx.set('Cache-Control', 'private, max-age=0, must-revalidate');
-			await next();
 		});
 
-		apiServer.use(bodyParser({
-			// リクエストが multipart/form-data でない限りはJSONだと見なす
-			detectJSON: ctx => !ctx.is('multipart/form-data'),
-		}));
-
-		// Init multer instance
-		const upload = multer({
-			storage: multer.diskStorage({}),
+		fastify.register(multipart, {
 			limits: {
 				fileSize: this.config.maxFileSize ?? 262144000,
 				files: 1,
 			},
 		});
 
-		// Init router
-		const router = new Router();
+		// Prevent cache
+		fastify.addHook('onRequest', (request, reply, done) => {
+			reply.header('Cache-Control', 'private, max-age=0, must-revalidate');
+			done();
+		});
 
-		/**
-		 * Register endpoint handlers
-		 */
 		for (const endpoint of endpoints) {
+			const ep = {
+				name: endpoint.name,
+				meta: endpoint.meta,
+				params: endpoint.params,
+				exec: this.moduleRef.get('ep:' + endpoint.name, { strict: false }).exec,
+			};
+
 			if (endpoint.meta.requireFile) {
-				router.post(`/${endpoint.name}`, upload.single('file'), this.apiCallService.handleRequest.bind(this.apiCallService, endpoint, handlers[endpoint.name]));
-			} else {
-				// 後方互換性のため
-				if (endpoint.name.includes('-')) {
-					router.post(`/${endpoint.name.replace(/-/g, '_')}`, this.apiCallService.handleRequest.bind(this.apiCallService, endpoint, handlers[endpoint.name]));
-
-					if (endpoint.meta.allowGet) {
-						router.get(`/${endpoint.name.replace(/-/g, '_')}`, this.apiCallService.handleRequest.bind(this.apiCallService, endpoint, handlers[endpoint.name]));
-					} else {
-						router.get(`/${endpoint.name.replace(/-/g, '_')}`, async ctx => { ctx.status = 405; });
+				fastify.all<{
+					Params: { endpoint: string; },
+					Body: Record<string, unknown>,
+					Querystring: Record<string, unknown>,
+				}>('/' + endpoint.name, (request, reply) => {
+					if (request.method === 'GET' && !endpoint.meta.allowGet) {
+						reply.code(405);
+						return;
 					}
-				}
-
-				router.post(`/${endpoint.name}`, this.apiCallService.handleRequest.bind(this.apiCallService, endpoint, handlers[endpoint.name]));
-
-				if (endpoint.meta.allowGet) {
-					router.get(`/${endpoint.name}`, this.apiCallService.handleRequest.bind(this.apiCallService, endpoint, handlers[endpoint.name]));
-				} else {
-					router.get(`/${endpoint.name}`, async ctx => { ctx.status = 405; });
-				}
+		
+					this.apiCallService.handleMultipartRequest(ep, request, reply);
+				});
+			} else {
+				fastify.all<{
+					Params: { endpoint: string; },
+					Body: Record<string, unknown>,
+					Querystring: Record<string, unknown>,
+				}>('/' + endpoint.name, (request, reply) => {
+					if (request.method === 'GET' && !endpoint.meta.allowGet) {
+						reply.code(405);
+						return;
+					}
+		
+					this.apiCallService.handleRequest(ep, request, reply);
+				});
 			}
 		}
 
-		router.post('/signup', ctx => this.signupApiServiceService.signup(ctx));
-		router.post('/signin', ctx => this.signinApiServiceService.signin(ctx));
-		router.post('/signup-pending', ctx => this.signupApiServiceService.signupPending(ctx));
+		fastify.post<{
+			Body: {
+				username: string;
+				password: string;
+				host?: string;
+				invitationCode?: string;
+				emailAddress?: string;
+				'hcaptcha-response'?: string;
+				'g-recaptcha-response'?: string;
+				'turnstile-response'?: string;
+			}
+		}>('/signup', (request, reply) => this.signupApiServiceService.signup(request, reply));
 
-		router.use(this.discordServerService.create().routes());
-		router.use(this.githubServerService.create().routes());
-		router.use(this.twitterServerService.create().routes());
+		fastify.post<{
+			Body: {
+				username: string;
+				password: string;
+				token?: string;
+				signature?: string;
+				authenticatorData?: string;
+				clientDataJSON?: string;
+				credentialId?: string;
+				challengeId?: string;
+			};
+		}>('/signin', (request, reply) => this.signinApiServiceService.signin(request, reply));
 
-		router.get('/v1/instance/peers', async ctx => {
+		fastify.post<{ Body: { code: string; } }>('/signup-pending', (request, reply) => this.signupApiServiceService.signupPending(request, reply));
+
+		fastify.register(this.discordServerService.create);
+		fastify.register(this.githubServerService.create);
+		fastify.register(this.twitterServerService.create);
+
+		fastify.get('/v1/instance/peers', async (request, reply) => {
 			const instances = await this.instancesRepository.find({
 				select: ['host'],
 			});
 
-			ctx.body = instances.map(instance => instance.host);
+			return instances.map(instance => instance.host);
 		});
 
-		router.post('/miauth/:session/check', async ctx => {
+		fastify.post<{ Params: { session: string; } }>('/miauth/:session/check', async (request, reply) => {
 			const token = await this.accessTokensRepository.findOneBy({
-				session: ctx.params.session,
+				session: request.params.session,
 			});
 
 			if (token && token.session != null && !token.fetched) {
@@ -135,26 +148,18 @@ export class ApiServerService {
 					fetched: true,
 				});
 
-				ctx.body = {
+				return {
 					ok: true,
 					token: token.token,
 					user: await this.userEntityService.pack(token.userId, null, { detail: true }),
 				};
 			} else {
-				ctx.body = {
+				return {
 					ok: false,
 				};
 			}
 		});
 
-		// Return 404 for unknown API
-		router.all('(.*)', async ctx => {
-			ctx.status = 404;
-		});
-
-		// Register router
-		apiServer.use(router.routes());
-
-		return apiServer;
+		done();
 	}
 }
diff --git a/packages/backend/src/server/api/AuthenticateService.ts b/packages/backend/src/server/api/AuthenticateService.ts
index 4ce9b91f42..ad387c4732 100644
--- a/packages/backend/src/server/api/AuthenticateService.ts
+++ b/packages/backend/src/server/api/AuthenticateService.ts
@@ -34,7 +34,7 @@ export class AuthenticateService {
 		this.appCache = new Cache<App>(Infinity);
 	}
 
-	public async authenticate(token: string | null): Promise<[CacheableLocalUser | null | undefined, AccessToken | null | undefined]> {
+	public async authenticate(token: string | null | undefined): Promise<[CacheableLocalUser | null | undefined, AccessToken | null | undefined]> {
 		if (token == null) {
 			return [null, null];
 		}
diff --git a/packages/backend/src/server/api/SigninApiService.ts b/packages/backend/src/server/api/SigninApiService.ts
index a5e2b09012..8b3d86e5a6 100644
--- a/packages/backend/src/server/api/SigninApiService.ts
+++ b/packages/backend/src/server/api/SigninApiService.ts
@@ -3,6 +3,7 @@ import { Inject, Injectable } from '@nestjs/common';
 import bcrypt from 'bcryptjs';
 import * as speakeasy from 'speakeasy';
 import { IsNull } from 'typeorm';
+import { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify';
 import { DI } from '@/di-symbols.js';
 import type { UserSecurityKeysRepository, SigninsRepository, UserProfilesRepository, AttestationChallengesRepository, UsersRepository } from '@/models/index.js';
 import type { Config } from '@/config.js';
@@ -12,7 +13,6 @@ import { IdService } from '@/core/IdService.js';
 import { TwoFactorAuthenticationService } from '@/core/TwoFactorAuthenticationService.js';
 import { RateLimiterService } from './RateLimiterService.js';
 import { SigninService } from './SigninService.js';
-import type Koa from 'koa';
 
 @Injectable()
 export class SigninApiService {
@@ -42,47 +42,60 @@ export class SigninApiService {
 	) {
 	}
 
-	public async signin(ctx: Koa.Context) {
-		ctx.set('Access-Control-Allow-Origin', this.config.url);
-		ctx.set('Access-Control-Allow-Credentials', 'true');
+	public async signin(
+		request: FastifyRequest<{
+			Body: {
+				username: string;
+				password: string;
+				token?: string;
+				signature?: string;
+				authenticatorData?: string;
+				clientDataJSON?: string;
+				credentialId?: string;
+				challengeId?: string;
+			};
+		}>,
+		reply: FastifyReply,
+	) {
+		reply.header('Access-Control-Allow-Origin', this.config.url);
+		reply.header('Access-Control-Allow-Credentials', 'true');
 
-		const body = ctx.request.body as any;
+		const body = request.body;
 		const username = body['username'];
 		const password = body['password'];
 		const token = body['token'];
 
 		function error(status: number, error: { id: string }) {
-			ctx.status = status;
-			ctx.body = { error };
+			reply.code(status);
+			return { error };
 		}
 
 		try {
 		// not more than 1 attempt per second and not more than 10 attempts per hour
-			await this.rateLimiterService.limit({ key: 'signin', duration: 60 * 60 * 1000, max: 10, minInterval: 1000 }, getIpHash(ctx.ip));
+			await this.rateLimiterService.limit({ key: 'signin', duration: 60 * 60 * 1000, max: 10, minInterval: 1000 }, getIpHash(request.ip));
 		} catch (err) {
-			ctx.status = 429;
-			ctx.body = {
+			reply.code(429);
+			return {
 				error: {
 					message: 'Too many failed attempts to sign in. Try again later.',
 					code: 'TOO_MANY_AUTHENTICATION_FAILURES',
 					id: '22d05606-fbcf-421a-a2db-b32610dcfd1b',
 				},
 			};
-			return;
 		}
 
 		if (typeof username !== 'string') {
-			ctx.status = 400;
+			reply.code(400);
 			return;
 		}
 
 		if (typeof password !== 'string') {
-			ctx.status = 400;
+			reply.code(400);
 			return;
 		}
 
 		if (token != null && typeof token !== 'string') {
-			ctx.status = 400;
+			reply.code(400);
 			return;
 		}
 
@@ -93,17 +106,15 @@ export class SigninApiService {
 		}) as ILocalUser;
 
 		if (user == null) {
-			error(404, {
+			return error(404, {
 				id: '6cc579cc-885d-43d8-95c2-b8c7fc963280',
 			});
-			return;
 		}
 
 		if (user.isSuspended) {
-			error(403, {
+			return error(403, {
 				id: 'e03a5f46-d309-4865-9b69-56282d94e1eb',
 			});
-			return;
 		}
 
 		const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id });
@@ -117,32 +128,29 @@ export class SigninApiService {
 				id: this.idService.genId(),
 				createdAt: new Date(),
 				userId: user.id,
-				ip: ctx.ip,
-				headers: ctx.headers,
+				ip: request.ip,
+				headers: request.headers,
 				success: false,
 			});
 
-			error(status ?? 500, failure ?? { id: '4e30e80c-e338-45a0-8c8f-44455efa3b76' });
+			return error(status ?? 500, failure ?? { id: '4e30e80c-e338-45a0-8c8f-44455efa3b76' });
 		};
 
 		if (!profile.twoFactorEnabled) {
 			if (same) {
-				this.signinService.signin(ctx, user);
-				return;
+				return this.signinService.signin(request, reply, user);
 			} else {
-				await fail(403, {
+				return await fail(403, {
 					id: '932c904e-9460-45b7-9ce6-7ed33be7eb2c',
 				});
-				return;
 			}
 		}
 
 		if (token) {
 			if (!same) {
-				await fail(403, {
+				return await fail(403, {
 					id: '932c904e-9460-45b7-9ce6-7ed33be7eb2c',
 				});
-				return;
 			}
 
 			const verified = (speakeasy as any).totp.verify({
@@ -153,20 +161,17 @@ export class SigninApiService {
 			});
 
 			if (verified) {
-				this.signinService.signin(ctx, user);
-				return;
+				return this.signinService.signin(request, reply, user);
 			} else {
-				await fail(403, {
+				return await fail(403, {
 					id: 'cdf1235b-ac71-46d4-a3a6-84ccce48df6f',
 				});
-				return;
 			}
-		} else if (body.credentialId) {
+		} else if (body.credentialId && body.clientDataJSON && body.authenticatorData && body.signature) {
 			if (!same && !profile.usePasswordLessLogin) {
-				await fail(403, {
+				return await fail(403, {
 					id: '932c904e-9460-45b7-9ce6-7ed33be7eb2c',
 				});
-				return;
 			}
 
 			const clientDataJSON = Buffer.from(body.clientDataJSON, 'hex');
@@ -179,10 +184,9 @@ export class SigninApiService {
 			});
 
 			if (!challenge) {
-				await fail(403, {
+				return await fail(403, {
 					id: '2715a88a-2125-4013-932f-aa6fe72792da',
 				});
-				return;
 			}
 
 			await this.attestationChallengesRepository.delete({
@@ -191,10 +195,9 @@ export class SigninApiService {
 			});
 
 			if (new Date().getTime() - challenge.createdAt.getTime() >= 5 * 60 * 1000) {
-				await fail(403, {
+				return await fail(403, {
 					id: '2715a88a-2125-4013-932f-aa6fe72792da',
 				});
-				return;
 			}
 
 			const securityKey = await this.userSecurityKeysRepository.findOneBy({
@@ -207,10 +210,9 @@ export class SigninApiService {
 			});
 
 			if (!securityKey) {
-				await fail(403, {
+				return await fail(403, {
 					id: '66269679-aeaf-4474-862b-eb761197e046',
 				});
-				return;
 			}
 
 			const isValid = this.twoFactorAuthenticationService.verifySignin({
@@ -223,20 +225,17 @@ export class SigninApiService {
 			});
 
 			if (isValid) {
-				this.signinService.signin(ctx, user);
-				return;
+				return this.signinService.signin(request, reply, user);
 			} else {
-				await fail(403, {
+				return await fail(403, {
 					id: '93b86c4b-72f9-40eb-9815-798928603d1e',
 				});
-				return;
 			}
 		} else {
 			if (!same && !profile.usePasswordLessLogin) {
-				await fail(403, {
+				return await fail(403, {
 					id: '932c904e-9460-45b7-9ce6-7ed33be7eb2c',
 				});
-				return;
 			}
 
 			const keys = await this.userSecurityKeysRepository.findBy({
@@ -244,10 +243,9 @@ export class SigninApiService {
 			});
 
 			if (keys.length === 0) {
-				await fail(403, {
+				return await fail(403, {
 					id: 'f27fd449-9af4-4841-9249-1f989b9fa4a4',
 				});
-				return;
 			}
 
 			// 32 byte challenge
@@ -266,15 +264,14 @@ export class SigninApiService {
 				registrationChallenge: false,
 			});
 
-			ctx.body = {
+			reply.code(200);
+			return {
 				challenge,
 				challengeId,
 				securityKeys: keys.map(key => ({
 					id: key.id,
 				})),
 			};
-			ctx.status = 200;
-			return;
 		}
 	// never get here
 	}
diff --git a/packages/backend/src/server/api/SigninService.ts b/packages/backend/src/server/api/SigninService.ts
index 3b96dfee6f..18a1d6c088 100644
--- a/packages/backend/src/server/api/SigninService.ts
+++ b/packages/backend/src/server/api/SigninService.ts
@@ -1,13 +1,12 @@
 import { Inject, Injectable } from '@nestjs/common';
+import { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify';
 import { DI } from '@/di-symbols.js';
-import type { SigninsRepository } from '@/models/index.js';
-import type { UsersRepository } from '@/models/index.js';
+import type { SigninsRepository, UsersRepository } from '@/models/index.js';
 import type { Config } from '@/config.js';
 import { IdService } from '@/core/IdService.js';
 import type { ILocalUser } from '@/models/entities/User.js';
 import { GlobalEventService } from '@/core/GlobalEventService.js';
 import { SigninEntityService } from '@/core/entities/SigninEntityService.js';
-import type Koa from 'koa';
 
 @Injectable()
 export class SigninService {
@@ -24,10 +23,25 @@ export class SigninService {
 	) {
 	}
 
-	public signin(ctx: Koa.Context, user: ILocalUser, redirect = false) {
+	public signin(request: FastifyRequest, reply: FastifyReply, user: ILocalUser, redirect = false) {
+		setImmediate(async () => {
+			// Append signin history
+			const record = await this.signinsRepository.insert({
+				id: this.idService.genId(),
+				createdAt: new Date(),
+				userId: user.id,
+				ip: request.ip,
+				headers: request.headers,
+				success: true,
+			}).then(x => this.signinsRepository.findOneByOrFail(x.identifiers[0]));
+	
+			// Publish signin event
+			this.globalEventService.publishMainStream(user.id, 'signin', await this.signinEntityService.pack(record));
+		});
+
 		if (redirect) {
 			//#region Cookie
-			ctx.cookies.set('igi', user.token!, {
+			reply.cookies.set('igi', user.token!, {
 				path: '/',
 				// SEE: https://github.com/koajs/koa/issues/974
 				// When using a SSL proxy it should be configured to add the "X-Forwarded-Proto: https" header
@@ -36,29 +50,14 @@ export class SigninService {
 			});
 			//#endregion
 	
-			ctx.redirect(this.config.url);
+			reply.redirect(this.config.url);
 		} else {
-			ctx.body = {
+			reply.code(200);
+			return {
 				id: user.id,
 				i: user.token,
 			};
-			ctx.status = 200;
 		}
-	
-		(async () => {
-			// Append signin history
-			const record = await this.signinsRepository.insert({
-				id: this.idService.genId(),
-				createdAt: new Date(),
-				userId: user.id,
-				ip: ctx.ip,
-				headers: ctx.headers,
-				success: true,
-			}).then(x => this.signinsRepository.findOneByOrFail(x.identifiers[0]));
-	
-			// Publish signin event
-			this.globalEventService.publishMainStream(user.id, 'signin', await this.signinEntityService.pack(record));
-		})();
 	}
 }
 
diff --git a/packages/backend/src/server/api/SignupApiService.ts b/packages/backend/src/server/api/SignupApiService.ts
index edb8e4e8e6..771858d091 100644
--- a/packages/backend/src/server/api/SignupApiService.ts
+++ b/packages/backend/src/server/api/SignupApiService.ts
@@ -1,6 +1,7 @@
 import { Inject, Injectable } from '@nestjs/common';
 import rndstr from 'rndstr';
 import bcrypt from 'bcryptjs';
+import { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify';
 import { DI } from '@/di-symbols.js';
 import type { RegistrationTicketsRepository, UserPendingsRepository, UserProfilesRepository, UsersRepository } from '@/models/index.js';
 import type { Config } from '@/config.js';
@@ -11,8 +12,8 @@ import { SignupService } from '@/core/SignupService.js';
 import { UserEntityService } from '@/core/entities/UserEntityService.js';
 import { EmailService } from '@/core/EmailService.js';
 import { ILocalUser } from '@/models/entities/User.js';
+import { FastifyReplyError } from '@/misc/fastify-reply-error.js';
 import { SigninService } from './SigninService.js';
-import type Koa from 'koa';
 
 @Injectable()
 export class SignupApiService {
@@ -42,8 +43,22 @@ export class SignupApiService {
 	) {
 	}
 
-	public async signup(ctx: Koa.Context) {
-		const body = ctx.request.body;
+	public async signup(
+		request: FastifyRequest<{
+			Body: {
+				username: string;
+				password: string;
+				host?: string;
+				invitationCode?: string;
+				emailAddress?: string;
+				'hcaptcha-response'?: string;
+				'g-recaptcha-response'?: string;
+				'turnstile-response'?: string;
+			}
+		}>,
+		reply: FastifyReply,
+	) {
+		const body = request.body;
 
 		const instance = await this.metaService.fetch(true);
 	
@@ -51,20 +66,20 @@ export class SignupApiService {
 		// ただしテスト時はこの機構は障害となるため無効にする
 		if (process.env.NODE_ENV !== 'test') {
 			if (instance.enableHcaptcha && instance.hcaptchaSecretKey) {
-				await this.captchaService.verifyHcaptcha(instance.hcaptchaSecretKey, body['hcaptcha-response']).catch(e => {
-					ctx.throw(400, e);
+				await this.captchaService.verifyHcaptcha(instance.hcaptchaSecretKey, body['hcaptcha-response']).catch(err => {
+					throw new FastifyReplyError(400, err);
 				});
 			}
 	
 			if (instance.enableRecaptcha && instance.recaptchaSecretKey) {
-				await this.captchaService.verifyRecaptcha(instance.recaptchaSecretKey, body['g-recaptcha-response']).catch(e => {
-					ctx.throw(400, e);
+				await this.captchaService.verifyRecaptcha(instance.recaptchaSecretKey, body['g-recaptcha-response']).catch(err => {
+					throw new FastifyReplyError(400, err);
 				});
 			}
 
 			if (instance.enableTurnstile && instance.turnstileSecretKey) {
-				await this.captchaService.verifyTurnstile(instance.turnstileSecretKey, body['turnstile-response']).catch(e => {
-					ctx.throw(400, e);
+				await this.captchaService.verifyTurnstile(instance.turnstileSecretKey, body['turnstile-response']).catch(err => {
+					throw new FastifyReplyError(400, err);
 				});
 			}
 		}
@@ -77,20 +92,20 @@ export class SignupApiService {
 	
 		if (instance.emailRequiredForSignup) {
 			if (emailAddress == null || typeof emailAddress !== 'string') {
-				ctx.status = 400;
+				reply.code(400);
 				return;
 			}
 	
-			const available = await this.emailService.validateEmailForAccount(emailAddress);
-			if (!available) {
-				ctx.status = 400;
+			const res = await this.emailService.validateEmailForAccount(emailAddress);
+			if (!res.available) {
+				reply.code(400);
 				return;
 			}
 		}
 	
 		if (instance.disableRegistration) {
 			if (invitationCode == null || typeof invitationCode !== 'string') {
-				ctx.status = 400;
+				reply.code(400);
 				return;
 			}
 	
@@ -99,7 +114,7 @@ export class SignupApiService {
 			});
 	
 			if (ticket == null) {
-				ctx.status = 400;
+				reply.code(400);
 				return;
 			}
 	
@@ -117,18 +132,18 @@ export class SignupApiService {
 				id: this.idService.genId(),
 				createdAt: new Date(),
 				code,
-				email: emailAddress,
+				email: emailAddress!,
 				username: username,
 				password: hash,
 			});
 	
 			const link = `${this.config.url}/signup-complete/${code}`;
 	
-			this.emailService.sendEmail(emailAddress, 'Signup',
+			this.emailService.sendEmail(emailAddress!, 'Signup',
 				`To complete signup, please click this link:<br><a href="${link}">${link}</a>`,
 				`To complete signup, please click this link: ${link}`);
 	
-			ctx.status = 204;
+			reply.code(204);
 		} else {
 			try {
 				const { account, secret } = await this.signupService.signup({
@@ -140,17 +155,18 @@ export class SignupApiService {
 					includeSecrets: true,
 				});
 	
-				(res as any).token = secret;
-	
-				ctx.body = res;
-			} catch (e) {
-				ctx.throw(400, e);
+				return {
+					...res,
+					token: secret,
+				};
+			} catch (err) {
+				throw new FastifyReplyError(400, err);
 			}
 		}
 	}
 
-	public async signupPending(ctx: Koa.Context) {
-		const body = ctx.request.body;
+	public async signupPending(request: FastifyRequest<{ Body: { code: string; } }>, reply: FastifyReply) {
+		const body = request.body;
 
 		const code = body['code'];
 
@@ -174,9 +190,9 @@ export class SignupApiService {
 				emailVerifyCode: null,
 			});
 
-			this.signinService.signin(ctx, account as ILocalUser);
-		} catch (e) {
-			ctx.throw(400, e);
+			this.signinService.signin(request, reply, account as ILocalUser);
+		} catch (err) {
+			throw new FastifyReplyError(400, err);
 		}
 	}
 }
diff --git a/packages/backend/src/server/api/endpoint-base.ts b/packages/backend/src/server/api/endpoint-base.ts
index 0a7f9b3008..b27329b9a9 100644
--- a/packages/backend/src/server/api/endpoint-base.ts
+++ b/packages/backend/src/server/api/endpoint-base.ts
@@ -14,23 +14,28 @@ ajv.addFormat('misskey:id', /^[a-zA-Z0-9]+$/);
 
 export type Response = Record<string, any> | void;
 
+type File = {
+	name: string | null;
+	path: string;
+};
+
 // TODO: paramsの型をT['params']のスキーマ定義から推論する
 type executor<T extends IEndpointMeta, Ps extends Schema> =
-	(params: SchemaType<Ps>, user: T['requireCredential'] extends true ? CacheableLocalUser : CacheableLocalUser | null, token: AccessToken | null, file?: any, cleanup?: () => any, ip?: string | null, headers?: Record<string, string> | null) =>
+	(params: SchemaType<Ps>, user: T['requireCredential'] extends true ? CacheableLocalUser : CacheableLocalUser | null, token: AccessToken | null, file?: File, cleanup?: () => any, ip?: string | null, headers?: Record<string, string> | null) =>
 		Promise<T['res'] extends undefined ? Response : SchemaType<NonNullable<T['res']>>>;
 
 export abstract class Endpoint<T extends IEndpointMeta, Ps extends Schema> {
-	public exec: (params: any, user: T['requireCredential'] extends true ? CacheableLocalUser : CacheableLocalUser | null, token: AccessToken | null, file?: any, ip?: string | null, headers?: Record<string, string> | null) => Promise<any>;
+	public exec: (params: any, user: T['requireCredential'] extends true ? CacheableLocalUser : CacheableLocalUser | null, token: AccessToken | null, file?: File, ip?: string | null, headers?: Record<string, string> | null) => Promise<any>;
 
 	constructor(meta: T, paramDef: Ps, cb: executor<T, Ps>) {
 		const validate = ajv.compile(paramDef);
 
-		this.exec = (params: any, user: T['requireCredential'] extends true ? CacheableLocalUser : CacheableLocalUser | null, token: AccessToken | null, file?: any, ip?: string | null, headers?: Record<string, string> | null) => {
+		this.exec = (params: any, user: T['requireCredential'] extends true ? CacheableLocalUser : CacheableLocalUser | null, token: AccessToken | null, file?: File, ip?: string | null, headers?: Record<string, string> | null) => {
 			let cleanup: undefined | (() => void) = undefined;
 	
 			if (meta.requireFile) {
 				cleanup = () => {
-					fs.unlink(file.path, () => {});
+					if (file) fs.unlink(file.path, () => {});
 				};
 	
 				if (file == null) return Promise.reject(new ApiError({
diff --git a/packages/backend/src/server/api/endpoints/drive/files/create.ts b/packages/backend/src/server/api/endpoints/drive/files/create.ts
index d394f5c3da..3f4485fc8b 100644
--- a/packages/backend/src/server/api/endpoints/drive/files/create.ts
+++ b/packages/backend/src/server/api/endpoints/drive/files/create.ts
@@ -78,8 +78,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 	) {
 		super(meta, paramDef, async (ps, me, _, file, cleanup, ip, headers) => {
 			// Get 'name' parameter
-			let name = ps.name ?? file.originalname;
-			if (name !== undefined && name !== null) {
+			let name = ps.name ?? file!.name ?? null;
+			if (name != null) {
 				name = name.trim();
 				if (name.length === 0) {
 					name = null;
@@ -88,8 +88,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 				} else if (!this.driveFileEntityService.validateFileName(name)) {
 					throw new ApiError(meta.errors.invalidFileName);
 				}
-			} else {
-				name = null;
 			}
 
 			const meta = await this.metaService.fetch();
@@ -98,7 +96,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 				// Create file
 				const driveFile = await this.driveService.addFile({
 					user: me,
-					path: file.path,
+					path: file!.path,
 					name,
 					comment: ps.comment,
 					folderId: ps.folderId,
diff --git a/packages/backend/src/server/api/integration/DiscordServerService.ts b/packages/backend/src/server/api/integration/DiscordServerService.ts
index 1fd103797c..93c22a6c0b 100644
--- a/packages/backend/src/server/api/integration/DiscordServerService.ts
+++ b/packages/backend/src/server/api/integration/DiscordServerService.ts
@@ -1,9 +1,9 @@
 import { Inject, Injectable } from '@nestjs/common';
 import Redis from 'ioredis';
-import Router from '@koa/router';
 import { OAuth2 } from 'oauth';
 import { v4 as uuid } from 'uuid';
 import { IsNull } from 'typeorm';
+import { FastifyInstance, FastifyRequest, FastifyReply, FastifyPluginOptions } from 'fastify';
 import type { Config } from '@/config.js';
 import type { UserProfilesRepository, UsersRepository } from '@/models/index.js';
 import { DI } from '@/di-symbols.js';
@@ -12,8 +12,8 @@ import type { ILocalUser } from '@/models/entities/User.js';
 import { GlobalEventService } from '@/core/GlobalEventService.js';
 import { MetaService } from '@/core/MetaService.js';
 import { UserEntityService } from '@/core/entities/UserEntityService.js';
+import { FastifyReplyError } from '@/misc/fastify-reply-error.js';
 import { SigninService } from '../SigninService.js';
-import type Koa from 'koa';
 
 @Injectable()
 export class DiscordServerService {
@@ -36,21 +36,18 @@ export class DiscordServerService {
 		private metaService: MetaService,
 		private signinService: SigninService,
 	) {
+		this.create = this.create.bind(this);
 	}
 
-	public create() {
-		const router = new Router();
-
-		router.get('/disconnect/discord', async ctx => {
-			if (!this.compareOrigin(ctx)) {
-				ctx.throw(400, 'invalid origin');
-				return;
+	public create(fastify: FastifyInstance, options: FastifyPluginOptions, done: (err?: Error) => void) {
+		fastify.get('/disconnect/discord', async (request, reply) => {
+			if (!this.compareOrigin(request)) {
+				throw new FastifyReplyError(400, 'invalid origin');
 			}
 
-			const userToken = this.getUserToken(ctx);
+			const userToken = this.getUserToken(request);
 			if (!userToken) {
-				ctx.throw(400, 'signin required');
-				return;
+				throw new FastifyReplyError(400, 'signin required');
 			}
 
 			const user = await this.usersRepository.findOneByOrFail({
@@ -66,13 +63,13 @@ export class DiscordServerService {
 				integrations: profile.integrations,
 			});
 
-			ctx.body = 'Discordの連携を解除しました :v:';
-
 			// Publish i updated event
 			this.globalEventService.publishMainStream(user.id, 'meUpdated', await this.userEntityService.pack(user, user, {
 				detail: true,
 				includeSecrets: true,
 			}));
+
+			return 'Discordの連携を解除しました :v:';
 		});
 
 		const getOAuth2 = async () => {
@@ -90,16 +87,14 @@ export class DiscordServerService {
 			}
 		};
 
-		router.get('/connect/discord', async ctx => {
-			if (!this.compareOrigin(ctx)) {
-				ctx.throw(400, 'invalid origin');
-				return;
+		fastify.get('/connect/discord', async (request, reply) => {
+			if (!this.compareOrigin(request)) {
+				throw new FastifyReplyError(400, 'invalid origin');
 			}
 
-			const userToken = this.getUserToken(ctx);
+			const userToken = this.getUserToken(request);
 			if (!userToken) {
-				ctx.throw(400, 'signin required');
-				return;
+				throw new FastifyReplyError(400, 'signin required');
 			}
 
 			const params = {
@@ -112,10 +107,10 @@ export class DiscordServerService {
 			this.redisClient.set(userToken, JSON.stringify(params));
 
 			const oauth2 = await getOAuth2();
-			ctx.redirect(oauth2!.getAuthorizeUrl(params));
+			reply.redirect(oauth2!.getAuthorizeUrl(params));
 		});
 
-		router.get('/signin/discord', async ctx => {
+		fastify.get('/signin/discord', async (request, reply) => {
 			const sessid = uuid();
 
 			const params = {
@@ -125,7 +120,7 @@ export class DiscordServerService {
 				response_type: 'code',
 			};
 
-			ctx.cookies.set('signin_with_discord_sid', sessid, {
+			reply.cookies.set('signin_with_discord_sid', sessid, {
 				path: '/',
 				secure: this.config.url.startsWith('https'),
 				httpOnly: true,
@@ -134,27 +129,25 @@ export class DiscordServerService {
 			this.redisClient.set(sessid, JSON.stringify(params));
 
 			const oauth2 = await getOAuth2();
-			ctx.redirect(oauth2!.getAuthorizeUrl(params));
+			reply.redirect(oauth2!.getAuthorizeUrl(params));
 		});
 
-		router.get('/dc/cb', async ctx => {
-			const userToken = this.getUserToken(ctx);
+		fastify.get('/dc/cb', async (request, reply) => {
+			const userToken = this.getUserToken(request);
 
 			const oauth2 = await getOAuth2();
 
 			if (!userToken) {
-				const sessid = ctx.cookies.get('signin_with_discord_sid');
+				const sessid = request.cookies.get('signin_with_discord_sid');
 
 				if (!sessid) {
-					ctx.throw(400, 'invalid session');
-					return;
+					throw new FastifyReplyError(400, 'invalid session');
 				}
 
-				const code = ctx.query.code;
+				const code = request.query.code;
 
 				if (!code || typeof code !== 'string') {
-					ctx.throw(400, 'invalid session');
-					return;
+					throw new FastifyReplyError(400, 'invalid session');
 				}
 
 				const { redirect_uri, state } = await new Promise<any>((res, rej) => {
@@ -164,9 +157,8 @@ export class DiscordServerService {
 					});
 				});
 
-				if (ctx.query.state !== state) {
-					ctx.throw(400, 'invalid session');
-					return;
+				if (request.query.state !== state) {
+					throw new FastifyReplyError(400, 'invalid session');
 				}
 
 				const { accessToken, refreshToken, expiresDate } = await new Promise<any>((res, rej) =>
@@ -192,8 +184,7 @@ export class DiscordServerService {
 				})) as Record<string, unknown>;
 
 				if (typeof id !== 'string' || typeof username !== 'string' || typeof discriminator !== 'string') {
-					ctx.throw(400, 'invalid session');
-					return;
+					throw new FastifyReplyError(400, 'invalid session');
 				}
 
 				const profile = await this.userProfilesRepository.createQueryBuilder()
@@ -202,8 +193,7 @@ export class DiscordServerService {
 					.getOne();
 
 				if (profile == null) {
-					ctx.throw(404, `@${username}#${discriminator}と連携しているMisskeyアカウントはありませんでした...`);
-					return;
+					throw new FastifyReplyError(404, `@${username}#${discriminator}と連携しているMisskeyアカウントはありませんでした...`);
 				}
 
 				await this.userProfilesRepository.update(profile.userId, {
@@ -220,13 +210,12 @@ export class DiscordServerService {
 					},
 				});
 
-				this.signinService.signin(ctx, await this.usersRepository.findOneBy({ id: profile.userId }) as ILocalUser, true);
+				return this.signinService.signin(request, reply, await this.usersRepository.findOneBy({ id: profile.userId }) as ILocalUser, true);
 			} else {
-				const code = ctx.query.code;
+				const code = request.query.code;
 
 				if (!code || typeof code !== 'string') {
-					ctx.throw(400, 'invalid session');
-					return;
+					throw new FastifyReplyError(400, 'invalid session');
 				}
 
 				const { redirect_uri, state } = await new Promise<any>((res, rej) => {
@@ -236,9 +225,8 @@ export class DiscordServerService {
 					});
 				});
 
-				if (ctx.query.state !== state) {
-					ctx.throw(400, 'invalid session');
-					return;
+				if (request.query.state !== state) {
+					throw new FastifyReplyError(400, 'invalid session');
 				}
 
 				const { accessToken, refreshToken, expiresDate } = await new Promise<any>((res, rej) =>
@@ -263,8 +251,7 @@ export class DiscordServerService {
 					'Authorization': `Bearer ${accessToken}`,
 				})) as Record<string, unknown>;
 				if (typeof id !== 'string' || typeof username !== 'string' || typeof discriminator !== 'string') {
-					ctx.throw(400, 'invalid session');
-					return;
+					throw new FastifyReplyError(400, 'invalid session');
 				}
 
 				const user = await this.usersRepository.findOneByOrFail({
@@ -288,29 +275,29 @@ export class DiscordServerService {
 					},
 				});
 
-				ctx.body = `Discord: @${username}#${discriminator} を、Misskey: @${user.username} に接続しました!`;
-
 				// Publish i updated event
 				this.globalEventService.publishMainStream(user.id, 'meUpdated', await this.userEntityService.pack(user, user, {
 					detail: true,
 					includeSecrets: true,
 				}));
+
+				return `Discord: @${username}#${discriminator} を、Misskey: @${user.username} に接続しました!`;
 			}
 		});
 
-		return router;
+		done();
 	}
 
-	private getUserToken(ctx: Koa.BaseContext): string | null {
-		return ((ctx.headers['cookie'] ?? '').match(/igi=(\w+)/) ?? [null, null])[1];
+	private getUserToken(request: FastifyRequest): string | null {
+		return ((request.headers['cookie'] ?? '').match(/igi=(\w+)/) ?? [null, null])[1];
 	}
 	
-	private compareOrigin(ctx: Koa.BaseContext): boolean {
+	private compareOrigin(request: FastifyRequest): boolean {
 		function normalizeUrl(url?: string): string {
 			return url ? url.endsWith('/') ? url.substr(0, url.length - 1) : url : '';
 		}
 	
-		const referer = ctx.headers['referer'];
+		const referer = request.headers['referer'];
 	
 		return (normalizeUrl(referer) === normalizeUrl(this.config.url));
 	}
diff --git a/packages/backend/src/server/api/integration/GithubServerService.ts b/packages/backend/src/server/api/integration/GithubServerService.ts
index 98d6230749..2fd20bf831 100644
--- a/packages/backend/src/server/api/integration/GithubServerService.ts
+++ b/packages/backend/src/server/api/integration/GithubServerService.ts
@@ -1,9 +1,9 @@
 import { Inject, Injectable } from '@nestjs/common';
 import Redis from 'ioredis';
-import Router from '@koa/router';
 import { OAuth2 } from 'oauth';
 import { v4 as uuid } from 'uuid';
 import { IsNull } from 'typeorm';
+import { FastifyInstance, FastifyRequest, FastifyReply, FastifyPluginOptions } from 'fastify';
 import type { Config } from '@/config.js';
 import type { UserProfilesRepository, UsersRepository } from '@/models/index.js';
 import { DI } from '@/di-symbols.js';
@@ -12,8 +12,8 @@ import type { ILocalUser } from '@/models/entities/User.js';
 import { GlobalEventService } from '@/core/GlobalEventService.js';
 import { MetaService } from '@/core/MetaService.js';
 import { UserEntityService } from '@/core/entities/UserEntityService.js';
+import { FastifyReplyError } from '@/misc/fastify-reply-error.js';
 import { SigninService } from '../SigninService.js';
-import type Koa from 'koa';
 
 @Injectable()
 export class GithubServerService {
@@ -36,21 +36,18 @@ export class GithubServerService {
 		private metaService: MetaService,
 		private signinService: SigninService,
 	) {
+		this.create = this.create.bind(this);
 	}
 
-	public create() {
-		const router = new Router();
-
-		router.get('/disconnect/github', async ctx => {
-			if (!this.compareOrigin(ctx)) {
-				ctx.throw(400, 'invalid origin');
-				return;
+	public create(fastify: FastifyInstance, options: FastifyPluginOptions, done: (err?: Error) => void) {
+		fastify.get('/disconnect/github', async (request, reply) => {
+			if (!this.compareOrigin(request)) {
+				throw new FastifyReplyError(400, 'invalid origin');
 			}
 
-			const userToken = this.getUserToken(ctx);
+			const userToken = this.getUserToken(request);
 			if (!userToken) {
-				ctx.throw(400, 'signin required');
-				return;
+				throw new FastifyReplyError(400, 'signin required');
 			}
 
 			const user = await this.usersRepository.findOneByOrFail({
@@ -66,13 +63,13 @@ export class GithubServerService {
 				integrations: profile.integrations,
 			});
 
-			ctx.body = 'GitHubの連携を解除しました :v:';
-
 			// Publish i updated event
 			this.globalEventService.publishMainStream(user.id, 'meUpdated', await this.userEntityService.pack(user, user, {
 				detail: true,
 				includeSecrets: true,
 			}));
+
+			return 'GitHubの連携を解除しました :v:';
 		});
 
 		const getOath2 = async () => {
@@ -90,16 +87,14 @@ export class GithubServerService {
 			}
 		};
 
-		router.get('/connect/github', async ctx => {
-			if (!this.compareOrigin(ctx)) {
-				ctx.throw(400, 'invalid origin');
-				return;
+		fastify.get('/connect/github', async (request, reply) => {
+			if (!this.compareOrigin(request)) {
+				throw new FastifyReplyError(400, 'invalid origin');
 			}
 
-			const userToken = this.getUserToken(ctx);
+			const userToken = this.getUserToken(request);
 			if (!userToken) {
-				ctx.throw(400, 'signin required');
-				return;
+				throw new FastifyReplyError(400, 'signin required');
 			}
 
 			const params = {
@@ -111,10 +106,10 @@ export class GithubServerService {
 			this.redisClient.set(userToken, JSON.stringify(params));
 
 			const oauth2 = await getOath2();
-			ctx.redirect(oauth2!.getAuthorizeUrl(params));
+			reply.redirect(oauth2!.getAuthorizeUrl(params));
 		});
 
-		router.get('/signin/github', async ctx => {
+		fastify.get('/signin/github', async (request, reply) => {
 			const sessid = uuid();
 
 			const params = {
@@ -123,7 +118,7 @@ export class GithubServerService {
 				state: uuid(),
 			};
 
-			ctx.cookies.set('signin_with_github_sid', sessid, {
+			reply.cookies.set('signin_with_github_sid', sessid, {
 				path: '/',
 				secure: this.config.url.startsWith('https'),
 				httpOnly: true,
@@ -132,27 +127,25 @@ export class GithubServerService {
 			this.redisClient.set(sessid, JSON.stringify(params));
 
 			const oauth2 = await getOath2();
-			ctx.redirect(oauth2!.getAuthorizeUrl(params));
+			reply.redirect(oauth2!.getAuthorizeUrl(params));
 		});
 
-		router.get('/gh/cb', async ctx => {
-			const userToken = this.getUserToken(ctx);
+		fastify.get('/gh/cb', async (request, reply) => {
+			const userToken = this.getUserToken(request);
 
 			const oauth2 = await getOath2();
 
 			if (!userToken) {
-				const sessid = ctx.cookies.get('signin_with_github_sid');
+				const sessid = request.cookies.get('signin_with_github_sid');
 
 				if (!sessid) {
-					ctx.throw(400, 'invalid session');
-					return;
+					throw new FastifyReplyError(400, 'invalid session');
 				}
 
-				const code = ctx.query.code;
+				const code = request.query.code;
 
 				if (!code || typeof code !== 'string') {
-					ctx.throw(400, 'invalid session');
-					return;
+					throw new FastifyReplyError(400, 'invalid session');
 				}
 
 				const { redirect_uri, state } = await new Promise<any>((res, rej) => {
@@ -162,9 +155,8 @@ export class GithubServerService {
 					});
 				});
 
-				if (ctx.query.state !== state) {
-					ctx.throw(400, 'invalid session');
-					return;
+				if (request.query.state !== state) {
+					throw new FastifyReplyError(400, 'invalid session');
 				}
 
 				const { accessToken } = await new Promise<{ accessToken: string }>((res, rej) =>
@@ -184,8 +176,7 @@ export class GithubServerService {
 					'Authorization': `bearer ${accessToken}`,
 				})) as Record<string, unknown>;
 				if (typeof login !== 'string' || typeof id !== 'string') {
-					ctx.throw(400, 'invalid session');
-					return;
+					throw new FastifyReplyError(400, 'invalid session');
 				}
 
 				const link = await this.userProfilesRepository.createQueryBuilder()
@@ -194,17 +185,15 @@ export class GithubServerService {
 					.getOne();
 
 				if (link == null) {
-					ctx.throw(404, `@${login}と連携しているMisskeyアカウントはありませんでした...`);
-					return;
+					throw new FastifyReplyError(404, `@${login}と連携しているMisskeyアカウントはありませんでした...`);
 				}
 
-				this.signinService.signin(ctx, await this.usersRepository.findOneBy({ id: link.userId }) as ILocalUser, true);
+				return this.signinService.signin(request, reply, await this.usersRepository.findOneBy({ id: link.userId }) as ILocalUser, true);
 			} else {
-				const code = ctx.query.code;
+				const code = request.query.code;
 
 				if (!code || typeof code !== 'string') {
-					ctx.throw(400, 'invalid session');
-					return;
+					throw new FastifyReplyError(400, 'invalid session');
 				}
 
 				const { redirect_uri, state } = await new Promise<any>((res, rej) => {
@@ -214,9 +203,8 @@ export class GithubServerService {
 					});
 				});
 
-				if (ctx.query.state !== state) {
-					ctx.throw(400, 'invalid session');
-					return;
+				if (request.query.state !== state) {
+					throw new FastifyReplyError(400, 'invalid session');
 				}
 
 				const { accessToken } = await new Promise<{ accessToken: string }>((res, rej) =>
@@ -238,8 +226,7 @@ export class GithubServerService {
 				})) as Record<string, unknown>;
 
 				if (typeof login !== 'string' || typeof id !== 'string') {
-					ctx.throw(400, 'invalid session');
-					return;
+					throw new FastifyReplyError(400, 'invalid session');
 				}
 
 				const user = await this.usersRepository.findOneByOrFail({
@@ -260,29 +247,29 @@ export class GithubServerService {
 					},
 				});
 
-				ctx.body = `GitHub: @${login} を、Misskey: @${user.username} に接続しました!`;
-
 				// Publish i updated event
 				this.globalEventService.publishMainStream(user.id, 'meUpdated', await this.userEntityService.pack(user, user, {
 					detail: true,
 					includeSecrets: true,
 				}));
+
+				return `GitHub: @${login} を、Misskey: @${user.username} に接続しました!`;
 			}
 		});
 
-		return router;
+		done();
 	}
 
-	private getUserToken(ctx: Koa.BaseContext): string | null {
-		return ((ctx.headers['cookie'] ?? '').match(/igi=(\w+)/) ?? [null, null])[1];
+	private getUserToken(request: FastifyRequest): string | null {
+		return ((request.headers['cookie'] ?? '').match(/igi=(\w+)/) ?? [null, null])[1];
 	}
 	
-	private compareOrigin(ctx: Koa.BaseContext): boolean {
+	private compareOrigin(request: FastifyRequest): boolean {
 		function normalizeUrl(url?: string): string {
 			return url ? url.endsWith('/') ? url.substr(0, url.length - 1) : url : '';
 		}
 	
-		const referer = ctx.headers['referer'];
+		const referer = request.headers['referer'];
 	
 		return (normalizeUrl(referer) === normalizeUrl(this.config.url));
 	}
diff --git a/packages/backend/src/server/api/integration/TwitterServerService.ts b/packages/backend/src/server/api/integration/TwitterServerService.ts
index 57c977bc54..a8447f9d49 100644
--- a/packages/backend/src/server/api/integration/TwitterServerService.ts
+++ b/packages/backend/src/server/api/integration/TwitterServerService.ts
@@ -1,6 +1,6 @@
 import { Inject, Injectable } from '@nestjs/common';
 import Redis from 'ioredis';
-import Router from '@koa/router';
+import { FastifyInstance, FastifyRequest, FastifyReply, FastifyPluginOptions } from 'fastify';
 import { v4 as uuid } from 'uuid';
 import { IsNull } from 'typeorm';
 import autwh from 'autwh';
@@ -12,8 +12,8 @@ import type { ILocalUser } from '@/models/entities/User.js';
 import { GlobalEventService } from '@/core/GlobalEventService.js';
 import { MetaService } from '@/core/MetaService.js';
 import { UserEntityService } from '@/core/entities/UserEntityService.js';
+import { FastifyReplyError } from '@/misc/fastify-reply-error.js';
 import { SigninService } from '../SigninService.js';
-import type Koa from 'koa';
 
 @Injectable()
 export class TwitterServerService {
@@ -36,21 +36,18 @@ export class TwitterServerService {
 		private metaService: MetaService,
 		private signinService: SigninService,
 	) {
+		this.create = this.create.bind(this);
 	}
 
-	public create() {
-		const router = new Router();
-
-		router.get('/disconnect/twitter', async ctx => {
-			if (!this.compareOrigin(ctx)) {
-				ctx.throw(400, 'invalid origin');
-				return;
+	public create(fastify: FastifyInstance, options: FastifyPluginOptions, done: (err?: Error) => void) {
+		fastify.get('/disconnect/twitter', async (request, reply) => {
+			if (!this.compareOrigin(request)) {
+				throw new FastifyReplyError(400, 'invalid origin');
 			}
 
-			const userToken = this.getUserToken(ctx);
+			const userToken = this.getUserToken(request);
 			if (userToken == null) {
-				ctx.throw(400, 'signin required');
-				return;
+				throw new FastifyReplyError(400, 'signin required');
 			}
 
 			const user = await this.usersRepository.findOneByOrFail({
@@ -66,13 +63,13 @@ export class TwitterServerService {
 				integrations: profile.integrations,
 			});
 
-			ctx.body = 'Twitterの連携を解除しました :v:';
-
 			// Publish i updated event
 			this.globalEventService.publishMainStream(user.id, 'meUpdated', await this.userEntityService.pack(user, user, {
 				detail: true,
 				includeSecrets: true,
 			}));
+
+			return 'Twitterの連携を解除しました :v:';
 		});
 
 		const getTwAuth = async () => {
@@ -89,25 +86,23 @@ export class TwitterServerService {
 			}
 		};
 
-		router.get('/connect/twitter', async ctx => {
-			if (!this.compareOrigin(ctx)) {
-				ctx.throw(400, 'invalid origin');
-				return;
+		fastify.get('/connect/twitter', async (request, reply) => {
+			if (!this.compareOrigin(request)) {
+				throw new FastifyReplyError(400, 'invalid origin');
 			}
 
-			const userToken = this.getUserToken(ctx);
+			const userToken = this.getUserToken(request);
 			if (userToken == null) {
-				ctx.throw(400, 'signin required');
-				return;
+				throw new FastifyReplyError(400, 'signin required');
 			}
 
 			const twAuth = await getTwAuth();
 			const twCtx = await twAuth!.begin();
 			this.redisClient.set(userToken, JSON.stringify(twCtx));
-			ctx.redirect(twCtx.url);
+			reply.redirect(twCtx.url);
 		});
 
-		router.get('/signin/twitter', async ctx => {
+		fastify.get('/signin/twitter', async (request, reply) => {
 			const twAuth = await getTwAuth();
 			const twCtx = await twAuth!.begin();
 
@@ -115,26 +110,25 @@ export class TwitterServerService {
 
 			this.redisClient.set(sessid, JSON.stringify(twCtx));
 
-			ctx.cookies.set('signin_with_twitter_sid', sessid, {
+			reply.cookies.set('signin_with_twitter_sid', sessid, {
 				path: '/',
 				secure: this.config.url.startsWith('https'),
 				httpOnly: true,
 			});
 
-			ctx.redirect(twCtx.url);
+			reply.redirect(twCtx.url);
 		});
 
-		router.get('/tw/cb', async ctx => {
-			const userToken = this.getUserToken(ctx);
+		fastify.get('/tw/cb', async (request, reply) => {
+			const userToken = this.getUserToken(request);
 
 			const twAuth = await getTwAuth();
 
 			if (userToken == null) {
-				const sessid = ctx.cookies.get('signin_with_twitter_sid');
+				const sessid = request.cookies.get('signin_with_twitter_sid');
 
 				if (sessid == null) {
-					ctx.throw(400, 'invalid session');
-					return;
+					throw new FastifyReplyError(400, 'invalid session');
 				}
 
 				const get = new Promise<any>((res, rej) => {
@@ -145,10 +139,9 @@ export class TwitterServerService {
 
 				const twCtx = await get;
 
-				const verifier = ctx.query.oauth_verifier;
+				const verifier = request.query.oauth_verifier;
 				if (!verifier || typeof verifier !== 'string') {
-					ctx.throw(400, 'invalid session');
-					return;
+					throw new FastifyReplyError(400, 'invalid session');
 				}
 
 				const result = await twAuth!.done(JSON.parse(twCtx), verifier);
@@ -159,17 +152,15 @@ export class TwitterServerService {
 					.getOne();
 
 				if (link == null) {
-					ctx.throw(404, `@${result.screenName}と連携しているMisskeyアカウントはありませんでした...`);
-					return;
+					throw new FastifyReplyError(404, `@${result.screenName}と連携しているMisskeyアカウントはありませんでした...`);
 				}
 
-				this.signinService.signin(ctx, await this.usersRepository.findOneBy({ id: link.userId }) as ILocalUser, true);
+				return this.signinService.signin(request, reply, await this.usersRepository.findOneBy({ id: link.userId }) as ILocalUser, true);
 			} else {
-				const verifier = ctx.query.oauth_verifier;
+				const verifier = request.query.oauth_verifier;
 
 				if (!verifier || typeof verifier !== 'string') {
-					ctx.throw(400, 'invalid session');
-					return;
+					throw new FastifyReplyError(400, 'invalid session');
 				}
 
 				const get = new Promise<any>((res, rej) => {
@@ -201,29 +192,29 @@ export class TwitterServerService {
 					},
 				});
 
-				ctx.body = `Twitter: @${result.screenName} を、Misskey: @${user.username} に接続しました!`;
-
 				// Publish i updated event
 				this.globalEventService.publishMainStream(user.id, 'meUpdated', await this.userEntityService.pack(user, user, {
 					detail: true,
 					includeSecrets: true,
 				}));
+
+				return `Twitter: @${result.screenName} を、Misskey: @${user.username} に接続しました!`;
 			}
 		});
 
-		return router;
+		done();
 	}
 
-	private getUserToken(ctx: Koa.BaseContext): string | null {
-		return ((ctx.headers['cookie'] ?? '').match(/igi=(\w+)/) ?? [null, null])[1];
+	private getUserToken(request: FastifyRequest): string | null {
+		return ((request.headers['cookie'] ?? '').match(/igi=(\w+)/) ?? [null, null])[1];
 	}
 	
-	private compareOrigin(ctx: Koa.BaseContext): boolean {
+	private compareOrigin(request: FastifyRequest): boolean {
 		function normalizeUrl(url?: string): string {
 			return url ? url.endsWith('/') ? url.substr(0, url.length - 1) : url : '';
 		}
 	
-		const referer = ctx.headers['referer'];
+		const referer = request.headers['referer'];
 	
 		return (normalizeUrl(referer) === normalizeUrl(this.config.url));
 	}
diff --git a/packages/backend/src/server/web/ClientServerService.ts b/packages/backend/src/server/web/ClientServerService.ts
index 8957a91309..4c3f2bfd36 100644
--- a/packages/backend/src/server/web/ClientServerService.ts
+++ b/packages/backend/src/server/web/ClientServerService.ts
@@ -3,16 +3,12 @@ import { fileURLToPath } from 'node:url';
 import { PathOrFileDescriptor, readFileSync } from 'node:fs';
 import { Inject, Injectable } from '@nestjs/common';
 import ms from 'ms';
-import Koa from 'koa';
-import Router from '@koa/router';
-import send from 'koa-send';
-import favicon from 'koa-favicon';
-import views from 'koa-views';
 import sharp from 'sharp';
-import { createBullBoard } from '@bull-board/api';
-import { BullAdapter } from '@bull-board/api/bullAdapter.js';
-import { KoaAdapter } from '@bull-board/koa';
+import pug from 'pug';
 import { In, IsNull } from 'typeorm';
+import { FastifyInstance, FastifyPluginOptions, FastifyReply } from 'fastify';
+import fastifyStatic from '@fastify/static';
+import fastifyView from '@fastify/view';
 import type { Config } from '@/config.js';
 import { getNoteSummary } from '@/misc/get-note-summary.js';
 import { DI } from '@/di-symbols.js';
@@ -84,9 +80,10 @@ export class ClientServerService {
 		@Inject('queue:objectStorage') public objectStorageQueue: ObjectStorageQueue,
 		@Inject('queue:webhookDeliver') public webhookDeliverQueue: WebhookDeliverQueue,
 	) {
+		this.createServer = this.createServer.bind(this);
 	}
 
-	private async manifestHandler(ctx: Koa.Context) {
+	private async manifestHandler(reply: FastifyReply) {
 		const res = deepClone(manifest);
 
 		const instance = await this.metaService.fetch(true);
@@ -95,27 +92,26 @@ export class ClientServerService {
 		res.name = instance.name ?? 'Misskey';
 		if (instance.themeColor) res.theme_color = instance.themeColor;
 
-		ctx.set('Cache-Control', 'max-age=300');
-		ctx.body = res;
+		reply.header('Cache-Control', 'max-age=300');
+		return (res);
 	}
 
-	public createApp() {
-		const app = new Koa();
-
+	public createServer(fastify: FastifyInstance, options: FastifyPluginOptions, done: (err?: Error) => void) {
+		/* TODO
 		//#region Bull Dashboard
 		const bullBoardPath = '/queue';
 
 		// Authenticate
-		app.use(async (ctx, next) => {
+		app.use(async (request, reply) => {
 			if (ctx.path === bullBoardPath || ctx.path.startsWith(bullBoardPath + '/')) {
 				const token = ctx.cookies.get('token');
 				if (token == null) {
-					ctx.status = 401;
+					reply.code(401);
 					return;
 				}
 				const user = await this.usersRepository.findOneBy({ token });
 				if (user == null || !(user.isAdmin || user.isModerator)) {
-					ctx.status = 403;
+					reply.code(403);
 					return;
 				}
 			}
@@ -140,83 +136,84 @@ export class ClientServerService {
 		serverAdapter.setBasePath(bullBoardPath);
 		app.use(serverAdapter.registerPlugin());
 		//#endregion
+		*/
 
-		// Init renderer
-		app.use(views(_dirname + '/views', {
-			extension: 'pug',
-			options: {
+		fastify.register(fastifyView, {
+			root: _dirname + '/views',
+			engine: {
+				pug: pug,
+			},
+			defaultContext: {
 				version: this.config.version,
 				getClientEntry: () => process.env.NODE_ENV === 'production' ?
 					this.config.clientEntry :
 					JSON.parse(readFileSync(`${_dirname}/../../../../../built/_client_dist_/manifest.json`, 'utf-8'))['src/init.ts'],
 				config: this.config,
 			},
-		}));
-
-		// Serve favicon
-		app.use(favicon(`${_dirname}/../../../assets/favicon.ico`));
-
-		// Common request handler
-		app.use(async (ctx, next) => {
-			// IFrameの中に入れられないようにする
-			ctx.set('X-Frame-Options', 'DENY');
-			await next();
 		});
 
-		// Init router
-		const router = new Router();
+		fastify.addHook('onRequest', (request, reply, done) => {
+			// クリックジャッキング防止のためiFrameの中に入れられないようにする
+			reply.header('X-Frame-Options', 'DENY');
+			done();
+		});
 
 		//#region static assets
 
-		router.get('/static-assets/(.*)', async ctx => {
-			await send(ctx as any, ctx.path.replace('/static-assets/', ''), {
-				root: staticAssets,
-				maxage: ms('7 days'),
-			});
+		fastify.register(fastifyStatic, {
+			root: _dirname,
+			serve: false,
 		});
 
-		router.get('/client-assets/(.*)', async ctx => {
-			await send(ctx as any, ctx.path.replace('/client-assets/', ''), {
-				root: clientAssets,
-				maxage: ms('7 days'),
-			});
+		fastify.register(fastifyStatic, {
+			root: staticAssets,
+			prefix: '/static-assets/',
+			maxAge: ms('7 days'),
+			decorateReply: false,
 		});
 
-		router.get('/assets/(.*)', async ctx => {
-			await send(ctx as any, ctx.path.replace('/assets/', ''), {
-				root: assets,
-				maxage: ms('7 days'),
-			});
+		fastify.register(fastifyStatic, {
+			root: clientAssets,
+			prefix: '/client-assets/',
+			maxAge: ms('7 days'),
+			decorateReply: false,
 		});
 
-		// Apple touch icon
-		router.get('/apple-touch-icon.png', async ctx => {
-			await send(ctx as any, '/apple-touch-icon.png', {
-				root: staticAssets,
-			});
+		fastify.register(fastifyStatic, {
+			root: assets,
+			prefix: '/assets/',
+			maxAge: ms('7 days'),
+			decorateReply: false,
 		});
 
-		router.get('/twemoji/(.*)', async ctx => {
-			const path = ctx.path.replace('/twemoji/', '');
+		fastify.get('/favicon.ico', async (request, reply) => {
+			return reply.sendFile('/favicon.ico', staticAssets);
+		});
+
+		fastify.get('/apple-touch-icon.png', async (request, reply) => {
+			return reply.sendFile('/apple-touch-icon.png', staticAssets);
+		});
+
+		fastify.get<{ Params: { path: string } }>('/twemoji/:path(.*)', async (request, reply) => {
+			const path = request.params.path;
 
 			if (!path.match(/^[0-9a-f-]+\.svg$/)) {
-				ctx.status = 404;
+				reply.code(404);
 				return;
 			}
 
-			ctx.set('Content-Security-Policy', 'default-src \'none\'; style-src \'unsafe-inline\'');
+			reply.header('Content-Security-Policy', 'default-src \'none\'; style-src \'unsafe-inline\'');
 
-			await send(ctx as any, path, {
-				root: `${_dirname}/../../../node_modules/@discordapp/twemoji/dist/svg/`,
-				maxage: ms('30 days'),
+			return await reply.sendFile(path, `${_dirname}/../../../node_modules/@discordapp/twemoji/dist/svg/`, {
+				maxAge: ms('30 days'),
 			});
 		});
 
-		router.get('/twemoji-badge/(.*)', async ctx => {
-			const path = ctx.path.replace('/twemoji-badge/', '');
+		fastify.get<{ Params: { path: string } }>('/twemoji-badge/:path(.*)', async (request, reply) => {
+			const path = request.params.path;
 
 			if (!path.match(/^[0-9a-f-]+\.png$/)) {
-				ctx.status = 404;
+				reply.code(404);
 				return;
 			}
 
@@ -249,44 +246,43 @@ export class ClientServerService {
 				.png()
 				.toBuffer();
 
-			ctx.set('Content-Security-Policy', 'default-src \'none\'; style-src \'unsafe-inline\'');
-			ctx.set('Cache-Control', 'max-age=2592000');
-			ctx.set('Content-Type', 'image/png');
-			ctx.body = buffer;
+			reply.header('Content-Security-Policy', 'default-src \'none\'; style-src \'unsafe-inline\'');
+			reply.header('Cache-Control', 'max-age=2592000');
+			reply.header('Content-Type', 'image/png');
+			return buffer;
 		});
 
 		// ServiceWorker
-		router.get('/sw.js', async ctx => {
-			await send(ctx as any, '/sw.js', {
-				root: swAssets,
-				maxage: ms('10 minutes'),
+		fastify.get('/sw.js', async (request, reply) => {
+			return await reply.sendFile('/sw.js', swAssets, {
+				maxAge: ms('10 minutes'),
 			});
 		});
 
 		// Manifest
-		router.get('/manifest.json', ctx => this.manifestHandler(ctx));
+		fastify.get('/manifest.json', async (request, reply) => await this.manifestHandler(reply));
 
-		router.get('/robots.txt', async ctx => {
-			await send(ctx as any, '/robots.txt', {
-				root: staticAssets,
-			});
+		fastify.get('/robots.txt', async (request, reply) => {
+			return await reply.sendFile('/robots.txt', staticAssets);
 		});
 
 		//#endregion
 
-		// Docs
-		router.get('/api-doc', async ctx => {
-			await send(ctx as any, '/redoc.html', {
-				root: staticAssets,
+		const renderBase = async (reply: FastifyReply) => {
+			const meta = await this.metaService.fetch();
+			reply.header('Cache-Control', 'public, max-age=15');
+			return await reply.view('base', {
+				img: meta.bannerUrl,
+				title: meta.name ?? 'Misskey',
+				instanceName: meta.name ?? 'Misskey',
+				desc: meta.description,
+				icon: meta.iconUrl,
+				themeColor: meta.themeColor,
 			});
-		});
+		};
 
 		// URL preview endpoint
-		router.get('/url', ctx => this.urlPreviewService.handle(ctx));
-
-		router.get('/api.json', async ctx => {
-			ctx.body = genOpenapiSpec();
-		});
+		fastify.get<{ Querystring: { url: string; lang: string; } }>('/url', (request, reply) => this.urlPreviewService.handle(request, reply));
 
 		const getFeed = async (acct: string) => {
 			const { username, host } = Acct.parse(acct);
@@ -300,45 +296,45 @@ export class ClientServerService {
 		};
 
 		// Atom
-		router.get('/@:user.atom', async ctx => {
-			const feed = await getFeed(ctx.params.user);
+		fastify.get<{ Params: { user: string; } }>('/@:user.atom', async (request, reply) => {
+			const feed = await getFeed(request.params.user);
 
 			if (feed) {
-				ctx.set('Content-Type', 'application/atom+xml; charset=utf-8');
-				ctx.body = feed.atom1();
+				reply.header('Content-Type', 'application/atom+xml; charset=utf-8');
+				return feed.atom1();
 			} else {
-				ctx.status = 404;
+				reply.code(404);
 			}
 		});
 
 		// RSS
-		router.get('/@:user.rss', async ctx => {
-			const feed = await getFeed(ctx.params.user);
+		fastify.get<{ Params: { user: string; } }>('/@:user.rss', async (request, reply) => {
+			const feed = await getFeed(request.params.user);
 
 			if (feed) {
-				ctx.set('Content-Type', 'application/rss+xml; charset=utf-8');
-				ctx.body = feed.rss2();
+				reply.header('Content-Type', 'application/rss+xml; charset=utf-8');
+				return feed.rss2();
 			} else {
-				ctx.status = 404;
+				reply.code(404);
 			}
 		});
 
 		// JSON
-		router.get('/@:user.json', async ctx => {
-			const feed = await getFeed(ctx.params.user);
+		fastify.get<{ Params: { user: string; } }>('/@:user.json', async (request, reply) => {
+			const feed = await getFeed(request.params.user);
 
 			if (feed) {
-				ctx.set('Content-Type', 'application/json; charset=utf-8');
-				ctx.body = feed.json1();
+				reply.header('Content-Type', 'application/json; charset=utf-8');
+				return feed.json1();
 			} else {
-				ctx.status = 404;
+				reply.code(404);
 			}
 		});
 
 		//#region SSR (for crawlers)
 		// User
-		router.get(['/@:user', '/@:user/:sub'], async (ctx, next) => {
-			const { username, host } = Acct.parse(ctx.params.user);
+		fastify.get<{ Params: { user: string; sub?: string; } }>('/@:user/:sub?', async (request, reply) => {
+			const { username, host } = Acct.parse(request.params.user);
 			const user = await this.usersRepository.findOneBy({
 				usernameLower: username.toLowerCase(),
 				host: host ?? IsNull(),
@@ -354,41 +350,41 @@ export class ClientServerService {
 						.map(field => field.value)
 					: [];
 
-				await ctx.render('user', {
+				reply.header('Cache-Control', 'public, max-age=15');
+				return await reply.view('user', {
 					user, profile, me,
 					avatarUrl: await this.userEntityService.getAvatarUrl(user),
-					sub: ctx.params.sub,
+					sub: request.params.sub,
 					instanceName: meta.name ?? 'Misskey',
 					icon: meta.iconUrl,
 					themeColor: meta.themeColor,
 				});
-				ctx.set('Cache-Control', 'public, max-age=15');
 			} else {
 				// リモートユーザーなので
 				// モデレータがAPI経由で参照可能にするために404にはしない
-				await next();
+				return await renderBase(reply);
 			}
 		});
 
-		router.get('/users/:user', async ctx => {
+		fastify.get<{ Params: { user: string; } }>('/users/:user', async (request, reply) => {
 			const user = await this.usersRepository.findOneBy({
-				id: ctx.params.user,
+				id: request.params.user,
 				host: IsNull(),
 				isSuspended: false,
 			});
 
 			if (user == null) {
-				ctx.status = 404;
+				reply.code(404);
 				return;
 			}
 
-			ctx.redirect(`/@${user.username}${ user.host == null ? '' : '@' + user.host}`);
+			reply.redirect(`/@${user.username}${ user.host == null ? '' : '@' + user.host}`);
 		});
 
 		// Note
-		router.get('/notes/:note', async (ctx, next) => {
+		fastify.get<{ Params: { note: string; } }>('/notes/:note', async (request, reply) => {
 			const note = await this.notesRepository.findOneBy({
-				id: ctx.params.note,
+				id: request.params.note,
 				visibility: In(['public', 'home']),
 			});
 
@@ -396,7 +392,8 @@ export class ClientServerService {
 				const _note = await this.noteEntityService.pack(note);
 				const profile = await this.userProfilesRepository.findOneByOrFail({ userId: note.userId });
 				const meta = await this.metaService.fetch();
-				await ctx.render('note', {
+				reply.header('Cache-Control', 'public, max-age=15');
+				return await reply.view('note', {
 					note: _note,
 					profile,
 					avatarUrl: await this.userEntityService.getAvatarUrl(await this.usersRepository.findOneByOrFail({ id: note.userId })),
@@ -406,18 +403,14 @@ export class ClientServerService {
 					icon: meta.iconUrl,
 					themeColor: meta.themeColor,
 				});
-
-				ctx.set('Cache-Control', 'public, max-age=15');
-
-				return;
+			} else {
+				return await renderBase(reply);
 			}
-
-			await next();
 		});
 
 		// Page
-		router.get('/@:user/pages/:page', async (ctx, next) => {
-			const { username, host } = Acct.parse(ctx.params.user);
+		fastify.get<{ Params: { user: string; page: string; } }>('/@:user/pages/:page', async (request, reply) => {
+			const { username, host } = Acct.parse(request.params.user);
 			const user = await this.usersRepository.findOneBy({
 				usernameLower: username.toLowerCase(),
 				host: host ?? IsNull(),
@@ -426,7 +419,7 @@ export class ClientServerService {
 			if (user == null) return;
 
 			const page = await this.pagesRepository.findOneBy({
-				name: ctx.params.page,
+				name: request.params.page,
 				userId: user.id,
 			});
 
@@ -434,7 +427,12 @@ export class ClientServerService {
 				const _page = await this.pageEntityService.pack(page);
 				const profile = await this.userProfilesRepository.findOneByOrFail({ userId: page.userId });
 				const meta = await this.metaService.fetch();
-				await ctx.render('page', {
+				if (['public'].includes(page.visibility)) {
+					reply.header('Cache-Control', 'public, max-age=15');
+				} else {
+					reply.header('Cache-Control', 'private, max-age=0, must-revalidate');
+				}
+				return await reply.view('page', {
 					page: _page,
 					profile,
 					avatarUrl: await this.userEntityService.getAvatarUrl(await this.usersRepository.findOneByOrFail({ id: page.userId })),
@@ -442,31 +440,24 @@ export class ClientServerService {
 					icon: meta.iconUrl,
 					themeColor: meta.themeColor,
 				});
-
-				if (['public'].includes(page.visibility)) {
-					ctx.set('Cache-Control', 'public, max-age=15');
-				} else {
-					ctx.set('Cache-Control', 'private, max-age=0, must-revalidate');
-				}
-
-				return;
+			} else {
+				return await renderBase(reply);
 			}
-
-			await next();
 		});
 
 		// Clip
 		// TODO: 非publicなclipのハンドリング
-		router.get('/clips/:clip', async (ctx, next) => {
+		fastify.get<{ Params: { clip: string; } }>('/clips/:clip', async (request, reply) => {
 			const clip = await this.clipsRepository.findOneBy({
-				id: ctx.params.clip,
+				id: request.params.clip,
 			});
 
 			if (clip) {
 				const _clip = await this.clipEntityService.pack(clip);
 				const profile = await this.userProfilesRepository.findOneByOrFail({ userId: clip.userId });
 				const meta = await this.metaService.fetch();
-				await ctx.render('clip', {
+				reply.header('Cache-Control', 'public, max-age=15');
+				return await reply.view('clip', {
 					clip: _clip,
 					profile,
 					avatarUrl: await this.userEntityService.getAvatarUrl(await this.usersRepository.findOneByOrFail({ id: clip.userId })),
@@ -474,24 +465,21 @@ export class ClientServerService {
 					icon: meta.iconUrl,
 					themeColor: meta.themeColor,
 				});
-
-				ctx.set('Cache-Control', 'public, max-age=15');
-
-				return;
+			} else {
+				return await renderBase(reply);
 			}
-
-			await next();
 		});
 
 		// Gallery post
-		router.get('/gallery/:post', async (ctx, next) => {
-			const post = await this.galleryPostsRepository.findOneBy({ id: ctx.params.post });
+		fastify.get<{ Params: { post: string; } }>('/gallery/:post', async (request, reply) => {
+			const post = await this.galleryPostsRepository.findOneBy({ id: request.params.post });
 
 			if (post) {
 				const _post = await this.galleryPostEntityService.pack(post);
 				const profile = await this.userProfilesRepository.findOneByOrFail({ userId: post.userId });
 				const meta = await this.metaService.fetch();
-				await ctx.render('gallery-post', {
+				reply.header('Cache-Control', 'public, max-age=15');
+				return await reply.view('gallery-post', {
 					post: _post,
 					profile,
 					avatarUrl: await this.userEntityService.getAvatarUrl(await this.usersRepository.findOneByOrFail({ id: post.userId })),
@@ -499,46 +487,39 @@ export class ClientServerService {
 					icon: meta.iconUrl,
 					themeColor: meta.themeColor,
 				});
-
-				ctx.set('Cache-Control', 'public, max-age=15');
-
-				return;
+			} else {
+				return await renderBase(reply);
 			}
-
-			await next();
 		});
 
 		// Channel
-		router.get('/channels/:channel', async (ctx, next) => {
+		fastify.get<{ Params: { channel: string; } }>('/channels/:channel', async (request, reply) => {
 			const channel = await this.channelsRepository.findOneBy({
-				id: ctx.params.channel,
+				id: request.params.channel,
 			});
 
 			if (channel) {
 				const _channel = await this.channelEntityService.pack(channel);
 				const meta = await this.metaService.fetch();
-				await ctx.render('channel', {
+				reply.header('Cache-Control', 'public, max-age=15');
+				return await reply.view('channel', {
 					channel: _channel,
 					instanceName: meta.name ?? 'Misskey',
 					icon: meta.iconUrl,
 					themeColor: meta.themeColor,
 				});
-
-				ctx.set('Cache-Control', 'public, max-age=15');
-
-				return;
+			} else {
+				return await renderBase(reply);
 			}
-
-			await next();
 		});
 		//#endregion
 
-		router.get('/_info_card_', async ctx => {
+		fastify.get('/_info_card_', async (request, reply) => {
 			const meta = await this.metaService.fetch(true);
 
-			ctx.remove('X-Frame-Options');
+			reply.removeHeader('X-Frame-Options');
 
-			await ctx.render('info-card', {
+			return await reply.view('info-card', {
 				version: this.config.version,
 				host: this.config.host,
 				meta: meta,
@@ -547,14 +528,14 @@ export class ClientServerService {
 			});
 		});
 
-		router.get('/bios', async ctx => {
-			await ctx.render('bios', {
+		fastify.get('/bios', async (request, reply) => {
+			return await reply.view('bios', {
 				version: this.config.version,
 			});
 		});
 
-		router.get('/cli', async ctx => {
-			await ctx.render('cli', {
+		fastify.get('/cli', async (request, reply) => {
+			return await reply.view('cli', {
 				version: this.config.version,
 			});
 		});
@@ -562,33 +543,21 @@ export class ClientServerService {
 		const override = (source: string, target: string, depth = 0) =>
 			[, ...target.split('/').filter(x => x), ...source.split('/').filter(x => x).splice(depth)].join('/');
 
-		router.get('/flush', async ctx => {
-			await ctx.render('flush');
+		fastify.get('/flush', async (request, reply) => {
+			return await reply.view('flush');
 		});
 
 		// streamingに非WebSocketリクエストが来た場合にbase htmlをキャシュ付きで返すと、Proxy等でそのパスがキャッシュされておかしくなる
-		router.get('/streaming', async ctx => {
-			ctx.status = 503;
-			ctx.set('Cache-Control', 'private, max-age=0');
+		fastify.get('/streaming', async (request, reply) => {
+			reply.code(503);
+			reply.header('Cache-Control', 'private, max-age=0');
 		});
 
 		// Render base html for all requests
-		router.get('(.*)', async ctx => {
-			const meta = await this.metaService.fetch();
-			await ctx.render('base', {
-				img: meta.bannerUrl,
-				title: meta.name ?? 'Misskey',
-				instanceName: meta.name ?? 'Misskey',
-				desc: meta.description,
-				icon: meta.iconUrl,
-				themeColor: meta.themeColor,
-			});
-			ctx.set('Cache-Control', 'public, max-age=15');
+		fastify.get('*', async (request, reply) => {
+			return await renderBase(reply);
 		});
 
-		// Register router
-		app.use(router.routes());
-
-		return app;
+		done();
 	}
 }
diff --git a/packages/backend/src/server/web/UrlPreviewService.ts b/packages/backend/src/server/web/UrlPreviewService.ts
index f5dddd2db7..69f52cc2f2 100644
--- a/packages/backend/src/server/web/UrlPreviewService.ts
+++ b/packages/backend/src/server/web/UrlPreviewService.ts
@@ -1,5 +1,6 @@
 import { Inject, Injectable } from '@nestjs/common';
 import summaly from 'summaly';
+import { FastifyRequest, FastifyReply } from 'fastify';
 import { DI } from '@/di-symbols.js';
 import type { UsersRepository } from '@/models/index.js';
 import type { Config } from '@/config.js';
@@ -8,7 +9,6 @@ import { HttpRequestService } from '@/core/HttpRequestService.js';
 import type Logger from '@/logger.js';
 import { query } from '@/misc/prelude/url.js';
 import { LoggerService } from '@/core/LoggerService.js';
-import type Koa from 'koa';
 
 @Injectable()
 export class UrlPreviewService {
@@ -39,16 +39,19 @@ export class UrlPreviewService {
 			: null;
 	}
 
-	public async handle(ctx: Koa.Context) {
-		const url = ctx.query.url;
+	public async handle(
+		request: FastifyRequest<{ Querystring: { url: string; lang: string; } }>,
+		reply: FastifyReply,
+	) {
+		const url = request.query.url;
 		if (typeof url !== 'string') {
-			ctx.status = 400;
+			reply.code(400);
 			return;
 		}
 	
-		const lang = ctx.query.lang;
+		const lang = request.query.lang;
 		if (Array.isArray(lang)) {
-			ctx.status = 400;
+			reply.code(400);
 			return;
 		}
 	
@@ -73,14 +76,14 @@ export class UrlPreviewService {
 			summary.thumbnail = this.wrap(summary.thumbnail);
 	
 			// Cache 7days
-			ctx.set('Cache-Control', 'max-age=604800, immutable');
+			reply.header('Cache-Control', 'max-age=604800, immutable');
 	
-			ctx.body = summary;
+			return summary;
 		} catch (err) {
 			this.logger.warn(`Failed to get preview of ${url}: ${err}`);
-			ctx.status = 200;
-			ctx.set('Cache-Control', 'max-age=86400, immutable');
-			ctx.body = '{}';
+			reply.code(200);
+			reply.header('Cache-Control', 'max-age=86400, immutable');
+			return {};
 		}
 	}
 }
diff --git a/packages/backend/src/server/web/bios.js b/packages/backend/src/server/web/bios.js
index d06dee801a..c2ce5c3814 100644
--- a/packages/backend/src/server/web/bios.js
+++ b/packages/backend/src/server/web/bios.js
@@ -10,7 +10,7 @@ window.onload = async () => {
 			if (i) data.i = i;
 	
 			// Send request
-			fetch(endpoint.indexOf('://') > -1 ? endpoint : `/api/${endpoint}`, {
+			window.fetch(endpoint.indexOf('://') > -1 ? endpoint : `/api/${endpoint}`, {
 				method: 'POST',
 				body: JSON.stringify(data),
 				credentials: 'omit',
diff --git a/packages/backend/src/server/web/boot.js b/packages/backend/src/server/web/boot.js
index 2aef689d3f..ffd8b8941c 100644
--- a/packages/backend/src/server/web/boot.js
+++ b/packages/backend/src/server/web/boot.js
@@ -42,7 +42,7 @@
 			}
 		}
 
-		const res = await fetch(`/assets/locales/${lang}.${v}.json`);
+		const res = await window.fetch(`/assets/locales/${lang}.${v}.json`);
 		if (res.status === 200) {
 			localStorage.setItem('lang', lang);
 			localStorage.setItem('locale', await res.text());
@@ -290,9 +290,13 @@
 	// eslint-disable-next-line no-inner-declarations
 	async function checkUpdate() {
 		try {
-			const res = await fetch('/api/meta', {
+			const res = await window.fetch('/api/meta', {
 				method: 'POST',
-				cache: 'no-cache'
+				cache: 'no-cache',
+				body: '{}',
+				headers: {
+					'Content-Type': 'application/json',
+				},
 			});
 
 			const meta = await res.json();
diff --git a/packages/client/src/account.ts b/packages/client/src/account.ts
index 10257b841f..e9c29d6b0d 100644
--- a/packages/client/src/account.ts
+++ b/packages/client/src/account.ts
@@ -33,12 +33,15 @@ export async function signout() {
 			const registration = await navigator.serviceWorker.ready;
 			const push = await registration.pushManager.getSubscription();
 			if (push) {
-				await fetch(`${apiUrl}/sw/unregister`, {
+				await window.fetch(`${apiUrl}/sw/unregister`, {
 					method: 'POST',
 					body: JSON.stringify({
 						i: $i.token,
 						endpoint: push.endpoint,
 					}),
+					headers: {
+						'Content-Type': 'application/json',
+					},
 				});
 			}
 		}
@@ -80,32 +83,35 @@ export async function removeAccount(id: Account['id']) {
 function fetchAccount(token: string): Promise<Account> {
 	return new Promise((done, fail) => {
 		// Fetch user
-		fetch(`${apiUrl}/i`, {
+		window.fetch(`${apiUrl}/i`, {
 			method: 'POST',
 			body: JSON.stringify({
 				i: token,
 			}),
+			headers: {
+				'Content-Type': 'application/json',
+			},
 		})
-		.then(res => res.json())
-		.then(res => {
-			if (res.error) {
-				if (res.error.id === 'a8c724b3-6e9c-4b46-b1a8-bc3ed6258370') {
-					showSuspendedDialog().then(() => {
-						signout();
-					});
+			.then(res => res.json())
+			.then(res => {
+				if (res.error) {
+					if (res.error.id === 'a8c724b3-6e9c-4b46-b1a8-bc3ed6258370') {
+						showSuspendedDialog().then(() => {
+							signout();
+						});
+					} else {
+						alert({
+							type: 'error',
+							title: i18n.ts.failedToFetchAccountInformation,
+							text: JSON.stringify(res.error),
+						});
+					}
 				} else {
-					alert({
-						type: 'error',
-						title: i18n.ts.failedToFetchAccountInformation,
-						text: JSON.stringify(res.error),
-					});
+					res.token = token;
+					done(res);
 				}
-			} else {
-				res.token = token;
-				done(res);
-			}
-		})
-		.catch(fail);
+			})
+			.catch(fail);
 	});
 }
 
diff --git a/packages/client/src/components/MkCropperDialog.vue b/packages/client/src/components/MkCropperDialog.vue
index 4b05a51252..ae18160dea 100644
--- a/packages/client/src/components/MkCropperDialog.vue
+++ b/packages/client/src/components/MkCropperDialog.vue
@@ -66,7 +66,7 @@ const ok = async () => {
 				formData.append('folderId', defaultStore.state.uploadFolder);
 			}
 
-			fetch(apiUrl + '/drive/files/create', {
+			window.fetch(apiUrl + '/drive/files/create', {
 				method: 'POST',
 				body: formData,
 			})
diff --git a/packages/client/src/components/MkUrlPreview.vue b/packages/client/src/components/MkUrlPreview.vue
index af27f644ed..8fd1ce133d 100644
--- a/packages/client/src/components/MkUrlPreview.vue
+++ b/packages/client/src/components/MkUrlPreview.vue
@@ -68,7 +68,7 @@ let player = $ref({
 let playerEnabled = $ref(false);
 let tweetId = $ref<string | null>(null);
 let tweetExpanded = $ref(props.detail);
-const embedId = `embed${Math.random().toString().replace(/\D/,'')}`;
+const embedId = `embed${Math.random().toString().replace(/\D/, '')}`;
 let tweetHeight = $ref(150);
 
 const requestUrl = new URL(props.url);
@@ -86,7 +86,7 @@ const requestLang = (lang || 'ja-JP').replace('ja-KS', 'ja-JP');
 
 requestUrl.hash = '';
 
-fetch(`/url?url=${encodeURIComponent(requestUrl.href)}&lang=${requestLang}`).then(res => {
+window.fetch(`/url?url=${encodeURIComponent(requestUrl.href)}&lang=${requestLang}`).then(res => {
 	res.json().then(info => {
 		if (info.url == null) return;
 		title = info.title;
diff --git a/packages/client/src/components/MkYoutubePlayer.vue b/packages/client/src/components/MkYoutubePlayer.vue
index a6840ce647..815dc82a27 100644
--- a/packages/client/src/components/MkYoutubePlayer.vue
+++ b/packages/client/src/components/MkYoutubePlayer.vue
@@ -39,7 +39,7 @@ const requestLang = (lang ?? 'ja-JP').replace('ja-KS', 'ja-JP');
 
 const ytFetch = (): void => {
 	fetching = true;
-	fetch(`/url?url=${encodeURIComponent(requestUrl.href)}&lang=${requestLang}`).then(res => {
+	window.fetch(`/url?url=${encodeURIComponent(requestUrl.href)}&lang=${requestLang}`).then(res => {
 		res.json().then(info => {
 			if (info.url == null) return;
 			title = info.title;
diff --git a/packages/client/src/components/page/page.post.vue b/packages/client/src/components/page/page.post.vue
index f655196359..954c7675bd 100644
--- a/packages/client/src/components/page/page.post.vue
+++ b/packages/client/src/components/page/page.post.vue
@@ -25,12 +25,12 @@ export default defineComponent({
 	props: {
 		block: {
 			type: Object as PropType<PostBlock>,
-			required: true
+			required: true,
 		},
 		hpml: {
 			type: Object as PropType<Hpml>,
-			required: true
-		}
+			required: true,
+		},
 	},
 	data() {
 		return {
@@ -44,8 +44,8 @@ export default defineComponent({
 			handler() {
 				this.text = this.hpml.interpolate(this.block.text);
 			},
-			deep: true
-		}
+			deep: true,
+		},
 	},
 	methods: {
 		upload() {
@@ -59,14 +59,14 @@ export default defineComponent({
 						formData.append('folderId', this.$store.state.uploadFolder);
 					}
 
-					fetch(apiUrl + '/drive/files/create', {
+					window.fetch(apiUrl + '/drive/files/create', {
 						method: 'POST',
 						body: formData,
 					})
-					.then(response => response.json())
-					.then(f => {
-						ok(f);
-					});
+						.then(response => response.json())
+						.then(f => {
+							ok(f);
+						});
 				});
 			});
 			os.promiseDialog(promise);
@@ -81,8 +81,8 @@ export default defineComponent({
 			}).then(() => {
 				this.posted = true;
 			});
-		}
-	}
+		},
+	},
 });
 </script>
 
diff --git a/packages/client/src/os.ts b/packages/client/src/os.ts
index 515fc47819..7e57dcb4af 100644
--- a/packages/client/src/os.ts
+++ b/packages/client/src/os.ts
@@ -29,11 +29,14 @@ export const api = ((endpoint: string, data: Record<string, any> = {}, token?: s
 		if (token !== undefined) (data as any).i = token;
 
 		// Send request
-		fetch(endpoint.indexOf('://') > -1 ? endpoint : `${apiUrl}/${endpoint}`, {
+		window.fetch(endpoint.indexOf('://') > -1 ? endpoint : `${apiUrl}/${endpoint}`, {
 			method: 'POST',
 			body: JSON.stringify(data),
 			credentials: 'omit',
 			cache: 'no-cache',
+			headers: {
+				'Content-Type': 'application/json',
+			},
 		}).then(async (res) => {
 			const body = res.status === 204 ? null : await res.json();
 
@@ -63,7 +66,7 @@ export const apiGet = ((endpoint: string, data: Record<string, any> = {}) => {
 
 	const promise = new Promise((resolve, reject) => {
 		// Send request
-		fetch(`${apiUrl}/${endpoint}?${query}`, {
+		window.fetch(`${apiUrl}/${endpoint}?${query}`, {
 			method: 'GET',
 			credentials: 'omit',
 			cache: 'default',
diff --git a/packages/client/src/ui/_common_/statusbar-rss.vue b/packages/client/src/ui/_common_/statusbar-rss.vue
index e75e13bb48..e7f88e4984 100644
--- a/packages/client/src/ui/_common_/statusbar-rss.vue
+++ b/packages/client/src/ui/_common_/statusbar-rss.vue
@@ -37,7 +37,7 @@ const fetching = ref(true);
 let key = $ref(0);
 
 const tick = () => {
-	fetch(`/api/fetch-rss?url=${props.url}`, {}).then(res => {
+	window.fetch(`/api/fetch-rss?url=${props.url}`, {}).then(res => {
 		res.json().then(feed => {
 			if (props.shuffle) {
 				shuffle(feed.items);
diff --git a/packages/client/src/widgets/rss-ticker.vue b/packages/client/src/widgets/rss-ticker.vue
index 58c16983c8..82a2f59ae9 100644
--- a/packages/client/src/widgets/rss-ticker.vue
+++ b/packages/client/src/widgets/rss-ticker.vue
@@ -83,7 +83,7 @@ const fetching = ref(true);
 let key = $ref(0);
 
 const tick = () => {
-	fetch(`/api/fetch-rss?url=${widgetProps.url}`, {}).then(res => {
+	window.fetch(`/api/fetch-rss?url=${widgetProps.url}`, {}).then(res => {
 		res.json().then(feed => {
 			if (widgetProps.shuffle) {
 				shuffle(feed.items);
diff --git a/packages/client/src/widgets/rss.vue b/packages/client/src/widgets/rss.vue
index 3258b6c028..f392a8249a 100644
--- a/packages/client/src/widgets/rss.vue
+++ b/packages/client/src/widgets/rss.vue
@@ -51,7 +51,7 @@ const items = ref([]);
 const fetching = ref(true);
 
 const tick = () => {
-	fetch(`/api/fetch-rss?url=${widgetProps.url}`, {}).then(res => {
+	window.fetch(`/api/fetch-rss?url=${widgetProps.url}`, {}).then(res => {
 		res.json().then(feed => {
 			items.value = feed.items;
 			fetching.value = false;
diff --git a/yarn.lock b/yarn.lock
index 61d81c0017..06bf9d8fd1 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -417,40 +417,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@bull-board/api@npm:4.3.1":
-  version: 4.3.1
-  resolution: "@bull-board/api@npm:4.3.1"
-  dependencies:
-    redis-info: ^3.0.8
-  checksum: 05113b1e888e79f8efecdffdc1043455fa6f8714c55a1e973d8a0a7f60cf574b00487b5b86324523ff91641784a55ff14c469edc8dd985295dcfc27cf55b4c4a
-  languageName: node
-  linkType: hard
-
-"@bull-board/koa@npm:4.3.1":
-  version: 4.3.1
-  resolution: "@bull-board/koa@npm:4.3.1"
-  dependencies:
-    "@bull-board/api": 4.3.1
-    "@bull-board/ui": 4.3.1
-    ejs: ^3.1.7
-    koa: ^2.13.1
-    koa-mount: ^4.0.0
-    koa-router: ^10.0.0
-    koa-static: ^5.0.0
-    koa-views: ^7.0.1
-  checksum: 08f198cdaaa28fe8e254288a0d4c13e9cd481a97e40e5e9152fb9094cbac54459e86901da5d90c46fe2dccf310f78a50ef8763bf5980b98d33180299c64fbc3f
-  languageName: node
-  linkType: hard
-
-"@bull-board/ui@npm:4.3.1":
-  version: 4.3.1
-  resolution: "@bull-board/ui@npm:4.3.1"
-  dependencies:
-    "@bull-board/api": 4.3.1
-  checksum: 7bc4787ba8f9e3dda5cb580b4374872bc7b0870a08a504cfc2f380a39dda164ae71518e7b1921e53ff8abc4224c0861504b26b63510b6c9c9d23d647bdab54b2
-  languageName: node
-  linkType: hard
-
 "@chainsafe/is-ip@npm:^2.0.1":
   version: 2.0.1
   resolution: "@chainsafe/is-ip@npm:2.0.1"
@@ -654,18 +620,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@elastic/elasticsearch@npm:7.17.0":
-  version: 7.17.0
-  resolution: "@elastic/elasticsearch@npm:7.17.0"
-  dependencies:
-    debug: ^4.3.1
-    hpagent: ^0.1.1
-    ms: ^2.1.3
-    secure-json-parse: ^2.4.0
-  checksum: 08113bcb14203c5700e6575cb720aa32f5573a1776e13b78bf101ffeae46308c3664b94f16f7e2c5ec26e14c459d8d1491e234940e635cddfe3ee61a52fb51f9
-  languageName: node
-  linkType: hard
-
 "@esbuild/android-arm@npm:0.15.16":
   version: 0.15.16
   resolution: "@esbuild/android-arm@npm:0.15.16"
@@ -704,6 +658,117 @@ __metadata:
   languageName: node
   linkType: hard
 
+"@fastify/accept-negotiator@npm:^1.0.0":
+  version: 1.0.0
+  resolution: "@fastify/accept-negotiator@npm:1.0.0"
+  checksum: 9b6be6bfd0f1475e4d06ffbd6359d7cf70841cc8e37abe6fe32f2e0e8185da6077da0d5c6610ade356ed3d4af4d3a642094b4861b123607987491bef2a384957
+  languageName: node
+  linkType: hard
+
+"@fastify/accepts@npm:4.0.1":
+  version: 4.0.1
+  resolution: "@fastify/accepts@npm:4.0.1"
+  dependencies:
+    accepts: ^1.3.5
+    fastify-plugin: ^4.0.0
+  checksum: bd14a998dececc66cbfc331aac6e9063c19baa8915e44288a621ef13737df5ca11e83f4be5767951ed6b8ddded2b2a40c19ec630c68a3ac9c299afe50d5e4153
+  languageName: node
+  linkType: hard
+
+"@fastify/ajv-compiler@npm:^3.3.1":
+  version: 3.4.0
+  resolution: "@fastify/ajv-compiler@npm:3.4.0"
+  dependencies:
+    ajv: ^8.11.0
+    ajv-formats: ^2.1.1
+    fast-uri: ^2.0.0
+  checksum: 3e03f9673f0f13ce343bfb4a84f4e908d12bd775a2b82ff4bdf09ac062d09c6b89b62df7f96fab970dd61f77a9e43be2908eb28cd59e27654b25931444bde825
+  languageName: node
+  linkType: hard
+
+"@fastify/busboy@npm:^1.0.0":
+  version: 1.1.0
+  resolution: "@fastify/busboy@npm:1.1.0"
+  dependencies:
+    text-decoding: ^1.0.0
+  checksum: 8ef01870c5e2ddae787fb8775c844b26e54c366732565287d5f7fb7d8c3c746bd4f0ad8f8695f4006e4b63af2fdd9a206ca74b2bc6c5d3c96d88abc07daa16f5
+  languageName: node
+  linkType: hard
+
+"@fastify/cors@npm:8.2.0":
+  version: 8.2.0
+  resolution: "@fastify/cors@npm:8.2.0"
+  dependencies:
+    fastify-plugin: ^4.0.0
+    mnemonist: 0.39.5
+  checksum: b2e30602d3aad7b2170a153b60e2b0dba8ad7df67ac3b7918374d202097f60b8a252baeafbf37f4323190fa87170960a3162aa6120540f825ae7750414c3feea
+  languageName: node
+  linkType: hard
+
+"@fastify/deepmerge@npm:^1.0.0":
+  version: 1.2.0
+  resolution: "@fastify/deepmerge@npm:1.2.0"
+  checksum: 40f39aa859dbf90cf5cd09b0a06e86783e68f8046baad51f79e42c77db4c6ffe436e130103ade6731d81df3916818d1437ad88d1a6c53c56e809aa1a910f4c9a
+  languageName: node
+  linkType: hard
+
+"@fastify/error@npm:^3.0.0":
+  version: 3.0.0
+  resolution: "@fastify/error@npm:3.0.0"
+  checksum: d9ea16db2d17e4d54f34ad2daf7bbd223fd3fd5682e55406f61dae66616a2fd79fa7585736e6e3b46e9dc60da6e96018f92ebb2f87fd100b4e8ad27308aa9c74
+  languageName: node
+  linkType: hard
+
+"@fastify/fast-json-stringify-compiler@npm:^4.1.0":
+  version: 4.1.0
+  resolution: "@fastify/fast-json-stringify-compiler@npm:4.1.0"
+  dependencies:
+    fast-json-stringify: ^5.0.0
+  checksum: 5f848f606e23b04904189bf98c44ccae70c4ceaa793d619d3804ba4a9969d4b9846ceef4ac8a53d536a1cf8f1d3c30a4602850a44fc62bdc1893e341442b6e4f
+  languageName: node
+  linkType: hard
+
+"@fastify/multipart@npm:7.3.0":
+  version: 7.3.0
+  resolution: "@fastify/multipart@npm:7.3.0"
+  dependencies:
+    "@fastify/busboy": ^1.0.0
+    "@fastify/deepmerge": ^1.0.0
+    "@fastify/error": ^3.0.0
+    end-of-stream: ^1.4.4
+    fastify-plugin: ^4.0.0
+    hexoid: ^1.0.0
+    secure-json-parse: ^2.4.0
+    stream-wormhole: ^1.1.0
+  checksum: 192fc4f0892c34d342a3673c6522e13c0987747c4972b52ea48ca7978ea54b5a892d4594778b643dc35f37f429496b13a4e244d8c7eef60a852fadb52144fcbe
+  languageName: node
+  linkType: hard
+
+"@fastify/static@npm:6.5.0":
+  version: 6.5.0
+  resolution: "@fastify/static@npm:6.5.0"
+  dependencies:
+    "@fastify/accept-negotiator": ^1.0.0
+    content-disposition: ^0.5.3
+    fastify-plugin: ^4.0.0
+    glob: ^8.0.1
+    p-limit: ^3.1.0
+    readable-stream: ^4.0.0
+    send: ^0.18.0
+  checksum: 31ef10916847c51fb4c360860f56acee1cbd7a896b66a05440d4eff70473c5b4cdf908b4719a5a22a876617fdc4a4e0fe3673bd5b40c9d61260a90e08abc1d3d
+  languageName: node
+  linkType: hard
+
+"@fastify/view@npm:7.1.2":
+  version: 7.1.2
+  resolution: "@fastify/view@npm:7.1.2"
+  dependencies:
+    fastify-plugin: ^4.0.0
+    hashlru: ^2.3.0
+  checksum: 0d5c960dc4241ca09faf4b990aabf6ccdac27348da2e75682a738d5665973a133fbf6907309bbf002f5cb36d55240b698283eac372f521cf2a482a9c80ff788d
+  languageName: node
+  linkType: hard
+
 "@fortawesome/fontawesome-free@npm:6.1.2":
   version: 6.1.2
   resolution: "@fortawesome/fontawesome-free@npm:6.1.2"
@@ -1110,37 +1175,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@koa/cors@npm:3.3.0":
-  version: 3.3.0
-  resolution: "@koa/cors@npm:3.3.0"
-  dependencies:
-    vary: ^1.1.2
-  checksum: bb49c680e0d151aec1b19c24c14d61b65f430eb379e63d83789602cc7d8e52706ebcd74867cdb60b1d50ddb6f3d59be04e1c46328fae5721aeaf50e0d4fc2d28
-  languageName: node
-  linkType: hard
-
-"@koa/multer@npm:3.0.0":
-  version: 3.0.0
-  resolution: "@koa/multer@npm:3.0.0"
-  peerDependencies:
-    multer: "*"
-  checksum: 7671ffed2ab23224b30b4378b44b2db1749d36f82216b0b67ae349fd6fe86b40c4a72c98412499e059f69916b454fba152451f140adf288866462ba765eb6c32
-  languageName: node
-  linkType: hard
-
-"@koa/router@npm:9.0.1":
-  version: 9.0.1
-  resolution: "@koa/router@npm:9.0.1"
-  dependencies:
-    debug: ^4.1.1
-    http-errors: ^1.7.3
-    koa-compose: ^4.1.0
-    methods: ^1.1.2
-    path-to-regexp: ^6.1.0
-  checksum: 0013bfd26c1acd44c772a08adae5edac8168c662fd2a06c7e174e2a899806908e73e077234c26888fad3ddc54478b27e5b8c7f5db41cd2e27f3b837c54b2f9b8
-  languageName: node
-  linkType: hard
-
 "@mapbox/node-pre-gyp@npm:1.0.9":
   version: 1.0.9
   resolution: "@mapbox/node-pre-gyp@npm:1.0.9"
@@ -1894,7 +1928,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@types/accepts@npm:*":
+"@types/accepts@npm:1.3.5":
   version: 1.3.5
   resolution: "@types/accepts@npm:1.3.5"
   dependencies:
@@ -1960,16 +1994,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@types/body-parser@npm:*":
-  version: 1.19.2
-  resolution: "@types/body-parser@npm:1.19.2"
-  dependencies:
-    "@types/connect": "*"
-    "@types/node": "*"
-  checksum: e17840c7d747a549f00aebe72c89313d09fbc4b632b949b2470c5cb3b1cb73863901ae84d9335b567a79ec5efcfb8a28ff8e3f36bc8748a9686756b6d5681f40
-  languageName: node
-  linkType: hard
-
 "@types/bull@npm:4.10.0":
   version: 4.10.0
   resolution: "@types/bull@npm:4.10.0"
@@ -2000,34 +2024,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@types/connect@npm:*":
-  version: 3.4.35
-  resolution: "@types/connect@npm:3.4.35"
-  dependencies:
-    "@types/node": "*"
-  checksum: fe81351470f2d3165e8b12ce33542eef89ea893e36dd62e8f7d72566dfb7e448376ae962f9f3ea888547ce8b55a40020ca0e01d637fab5d99567673084542641
-  languageName: node
-  linkType: hard
-
-"@types/content-disposition@npm:*":
-  version: 0.5.5
-  resolution: "@types/content-disposition@npm:0.5.5"
-  checksum: fdf7379db1d509990bcf9a21d85f05aad878596f28b1418f9179f6436cb22513262c670ce88c6055054a7f5804a9303eeacb70aa59a5e11ffdc1434559db9692
-  languageName: node
-  linkType: hard
-
-"@types/cookies@npm:*":
-  version: 0.7.7
-  resolution: "@types/cookies@npm:0.7.7"
-  dependencies:
-    "@types/connect": "*"
-    "@types/express": "*"
-    "@types/keygrip": "*"
-    "@types/node": "*"
-  checksum: d3759efc1182cb0651808570ae13638677b67b0ea724eef7b174e58ffe6ea044b62c7c2715e532f76f88fce4dd8101ed32ac6fbb73226db654017924e8a2a1e6
-  languageName: node
-  linkType: hard
-
 "@types/disposable-email-domains@npm:^1.0.1":
   version: 1.0.2
   resolution: "@types/disposable-email-domains@npm:1.0.2"
@@ -2056,29 +2052,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@types/express-serve-static-core@npm:^4.17.18":
-  version: 4.17.31
-  resolution: "@types/express-serve-static-core@npm:4.17.31"
-  dependencies:
-    "@types/node": "*"
-    "@types/qs": "*"
-    "@types/range-parser": "*"
-  checksum: 009bfbe1070837454a1056aa710d0390ee5fb8c05dfe5a1691cc3e2ca88dc256f80e1ca27cb51a978681631d2f6431bfc9ec352ea46dd0c6eb183d0170bde5df
-  languageName: node
-  linkType: hard
-
-"@types/express@npm:*":
-  version: 4.17.14
-  resolution: "@types/express@npm:4.17.14"
-  dependencies:
-    "@types/body-parser": "*"
-    "@types/express-serve-static-core": ^4.17.18
-    "@types/qs": "*"
-    "@types/serve-static": "*"
-  checksum: 15c1af46d02de834e4a225eccaa9d85c0370fdbb3ed4e1bc2d323d24872309961542b993ae236335aeb3e278630224a6ea002078d39e651d78a3b0356b1eaa79
-  languageName: node
-  linkType: hard
-
 "@types/fluent-ffmpeg@npm:2.1.20":
   version: 2.1.20
   resolution: "@types/fluent-ffmpeg@npm:2.1.20"
@@ -2138,13 +2111,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@types/http-assert@npm:*":
-  version: 1.5.3
-  resolution: "@types/http-assert@npm:1.5.3"
-  checksum: 9553e5a0b8bcfdac4b51d3fa3b89a91b5450171861a667a5b4c47204e0f4a1ca865d97396e6ceaf220e87b64d06b7a8bad7bfba15ef97acb41a87507c9940dbc
-  languageName: node
-  linkType: hard
-
 "@types/http-cache-semantics@npm:*, @types/http-cache-semantics@npm:^4.0.1":
   version: 4.0.1
   resolution: "@types/http-cache-semantics@npm:4.0.1"
@@ -2152,13 +2118,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@types/http-errors@npm:*":
-  version: 2.0.1
-  resolution: "@types/http-errors@npm:2.0.1"
-  checksum: 3bb0c50b0a652e679a84c30cd0340d696c32ef6558518268c238840346c077f899315daaf1c26c09c57ddd5dc80510f2a7f46acd52bf949e339e35ed3ee9654f
-  languageName: node
-  linkType: hard
-
 "@types/istanbul-lib-coverage@npm:*, @types/istanbul-lib-coverage@npm:^2.0.0, @types/istanbul-lib-coverage@npm:^2.0.1":
   version: 2.0.4
   resolution: "@types/istanbul-lib-coverage@npm:2.0.4"
@@ -2247,13 +2206,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@types/keygrip@npm:*":
-  version: 1.0.2
-  resolution: "@types/keygrip@npm:1.0.2"
-  checksum: 60bc2738a4f107070ee3d96f44709cb38f3a96c7ccabab09f56c1b2b4d85f869fd8fb9f1f2937e863d0e9e781f005c2223b823bf32b859185b4f52370c352669
-  languageName: node
-  linkType: hard
-
 "@types/keyv@npm:^3.1.4":
   version: 3.1.4
   resolution: "@types/keyv@npm:3.1.4"
@@ -2263,121 +2215,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@types/koa-bodyparser@npm:4.3.8":
-  version: 4.3.8
-  resolution: "@types/koa-bodyparser@npm:4.3.8"
-  dependencies:
-    "@types/koa": "*"
-  checksum: df4501f4c29e24e6ffe4149accaaa4ac3aee9e0cac8fe0239f53f36bff4acfde4ffb4294212453efaa8a4231c66d139a9577f0d25cc491832c1b2904a5943f89
-  languageName: node
-  linkType: hard
-
-"@types/koa-compose@npm:*":
-  version: 3.2.5
-  resolution: "@types/koa-compose@npm:3.2.5"
-  dependencies:
-    "@types/koa": "*"
-  checksum: 5d1147c4b057eb158195f442f0384f06503f3e69dba99fb517b30a05261a9f92928945c12bb1cfc17a5b7d60db003f38b455a3a9b125f12e4fc81fffa396b3cf
-  languageName: node
-  linkType: hard
-
-"@types/koa-cors@npm:0.0.2":
-  version: 0.0.2
-  resolution: "@types/koa-cors@npm:0.0.2"
-  dependencies:
-    "@types/koa": "*"
-  checksum: 7218bd8f4600fede227626e01fabe2022c691ee8721945792eb3dba3b348b10ddc438c3a679734de783172be512eb6b780d0600ed7052c3f881ed234a601656e
-  languageName: node
-  linkType: hard
-
-"@types/koa-favicon@npm:2.0.21":
-  version: 2.0.21
-  resolution: "@types/koa-favicon@npm:2.0.21"
-  dependencies:
-    "@types/koa": "*"
-  checksum: 7e3da0dd430a96a007845be4d0d918281d5177ee36511c182f23048179168aac9b8b1fcd2a3de0b6e0f89b70fcb2a23c7b278de735e6daf71ef5cdea50761593
-  languageName: node
-  linkType: hard
-
-"@types/koa-logger@npm:3.1.2":
-  version: 3.1.2
-  resolution: "@types/koa-logger@npm:3.1.2"
-  dependencies:
-    "@types/koa": "*"
-  checksum: 8e4cfcdb2491052bbff35c8f0a1f0bf839e966c3903afcf39656bf21bd3089b1a50945ce6a92bea430a83c9341d714c968360953d3a52a5cc10cdb3fb0af4218
-  languageName: node
-  linkType: hard
-
-"@types/koa-mount@npm:4.0.1":
-  version: 4.0.1
-  resolution: "@types/koa-mount@npm:4.0.1"
-  dependencies:
-    "@types/koa": "*"
-  checksum: c010bfe6b2d81e6b1ca163b7699a5a0c90414079fcbc1d44c4005c896486db0d22b8220bd5f68a1cca7be481dba6a3b4f1c05b6affd80954c724d81170532f33
-  languageName: node
-  linkType: hard
-
-"@types/koa-send@npm:4.1.3":
-  version: 4.1.3
-  resolution: "@types/koa-send@npm:4.1.3"
-  dependencies:
-    "@types/koa": "*"
-  checksum: f20f6a0dcccd0d090348c7cce3635220cc82420b9579fa521dc6deae23c242aa8adb760a5a3fc84d7590a7f393b41b71b18312f9519c1c4a0b16ee24aae2e104
-  languageName: node
-  linkType: hard
-
-"@types/koa-views@npm:7.0.0":
-  version: 7.0.0
-  resolution: "@types/koa-views@npm:7.0.0"
-  dependencies:
-    koa-views: "*"
-  checksum: 03253380413e82806ef14c8f3ecadd659e0b00e4e8483fa9d8fe52f369edbfff02a779a5e09fca11eeaabf555dcc139378c8b123d1e4fc49125cb14ed27b3cab
-  languageName: node
-  linkType: hard
-
-"@types/koa@npm:*, @types/koa@npm:2.13.5":
-  version: 2.13.5
-  resolution: "@types/koa@npm:2.13.5"
-  dependencies:
-    "@types/accepts": "*"
-    "@types/content-disposition": "*"
-    "@types/cookies": "*"
-    "@types/http-assert": "*"
-    "@types/http-errors": "*"
-    "@types/keygrip": "*"
-    "@types/koa-compose": "*"
-    "@types/node": "*"
-  checksum: e3b634d934b79ce8f394bf4130511596081f9c073dbfb4309aa32e4c421c47049a002b65111f8d9687eabec55d5a27b1b9ae0699afa83894cb7032c3536bfa17
-  languageName: node
-  linkType: hard
-
-"@types/koa__cors@npm:3.3.0":
-  version: 3.3.0
-  resolution: "@types/koa__cors@npm:3.3.0"
-  dependencies:
-    "@types/koa": "*"
-  checksum: c1aeb10b070e72b6c01a2f6abb4b0a936017794ef4eab3469697a4e24ef2054bc371519afa90c8e6c5ea9dbeda58395a64400bd499c3fda207cb593b751b44ca
-  languageName: node
-  linkType: hard
-
-"@types/koa__multer@npm:2.0.4":
-  version: 2.0.4
-  resolution: "@types/koa__multer@npm:2.0.4"
-  dependencies:
-    "@types/koa": "*"
-  checksum: 4a945061a6a44ef981081132e85ce9d0c171c4a895439b511313d628f0567d2624635166ac3e2455bdc216b15da83819ab52537f9609bfa1540ffc0d1b09519b
-  languageName: node
-  linkType: hard
-
-"@types/koa__router@npm:8.0.11":
-  version: 8.0.11
-  resolution: "@types/koa__router@npm:8.0.11"
-  dependencies:
-    "@types/koa": "*"
-  checksum: 81f55ed77273871728c81a20fa546ee906bebfe72fd72e3723d983a19504eb7d9578908a0fb8ef230764c5495031412df4245eee93479161bd7bd5135ca1ea04
-  languageName: node
-  linkType: hard
-
 "@types/long@npm:^4.0.1":
   version: 4.0.2
   resolution: "@types/long@npm:4.0.2"
@@ -2399,13 +2236,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@types/mime@npm:*":
-  version: 3.0.1
-  resolution: "@types/mime@npm:3.0.1"
-  checksum: 4040fac73fd0cea2460e29b348c1a6173da747f3a87da0dbce80dd7a9355a3d0e51d6d9a401654f3e5550620e3718b5a899b2ec1debf18424e298a2c605346e7
-  languageName: node
-  linkType: hard
-
 "@types/minimatch@npm:*":
   version: 5.1.2
   resolution: "@types/minimatch@npm:5.1.2"
@@ -2526,13 +2356,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@types/qs@npm:*":
-  version: 6.9.7
-  resolution: "@types/qs@npm:6.9.7"
-  checksum: 7fd6f9c25053e9b5bb6bc9f9f76c1d89e6c04f7707a7ba0e44cc01f17ef5284adb82f230f542c2d5557d69407c9a40f0f3515e8319afd14e1e16b5543ac6cdba
-  languageName: node
-  linkType: hard
-
 "@types/random-seed@npm:0.3.3":
   version: 0.3.3
   resolution: "@types/random-seed@npm:0.3.3"
@@ -2540,13 +2363,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@types/range-parser@npm:*":
-  version: 1.2.4
-  resolution: "@types/range-parser@npm:1.2.4"
-  checksum: b7c0dfd5080a989d6c8bb0b6750fc0933d9acabeb476da6fe71d8bdf1ab65e37c136169d84148034802f48378ab94e3c37bb4ef7656b2bec2cb9c0f8d4146a95
-  languageName: node
-  linkType: hard
-
 "@types/ratelimiter@npm:3.4.4":
   version: 3.4.4
   resolution: "@types/ratelimiter@npm:3.4.4"
@@ -2609,16 +2425,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@types/serve-static@npm:*":
-  version: 1.15.0
-  resolution: "@types/serve-static@npm:1.15.0"
-  dependencies:
-    "@types/mime": "*"
-    "@types/node": "*"
-  checksum: b6ac93d471fb0f53ddcac1f9b67572a09cd62806f7db5855244b28f6f421139626f24799392566e97d1ffc61b12f9de7f30380c39fcae3c8a161fe161d44edf2
-  languageName: node
-  linkType: hard
-
 "@types/sharp@npm:0.31.0":
   version: 0.31.0
   resolution: "@types/sharp@npm:0.31.0"
@@ -2727,6 +2533,15 @@ __metadata:
   languageName: node
   linkType: hard
 
+"@types/vary@npm:1.1.0":
+  version: 1.1.0
+  resolution: "@types/vary@npm:1.1.0"
+  dependencies:
+    "@types/node": "*"
+  checksum: 6f434a96966ebd4b091592a085cf326f9bfe417aca8714092cc8d3a6d9da775d03c9c799b191f9bbb91ee558831f8b47b2a62f7d7ee0097e8aeee2466d01d515
+  languageName: node
+  linkType: hard
+
 "@types/vinyl-fs@npm:*":
   version: 2.4.12
   resolution: "@types/vinyl-fs@npm:2.4.12"
@@ -3088,7 +2903,14 @@ __metadata:
   languageName: node
   linkType: hard
 
-"accepts@npm:^1.3.5":
+"abstract-logging@npm:^2.0.1":
+  version: 2.0.1
+  resolution: "abstract-logging@npm:2.0.1"
+  checksum: 6967d15e5abbafd17f56eaf30ba8278c99333586fa4f7935fd80e93cfdc006c37fcc819c5d63ee373a12e6cb2d0417f7c3c6b9e42b957a25af9937d26749415e
+  languageName: node
+  linkType: hard
+
+"accepts@npm:^1.3.5, accepts@npm:^1.3.8":
   version: 1.3.8
   resolution: "accepts@npm:1.3.8"
   dependencies:
@@ -3188,6 +3010,20 @@ __metadata:
   languageName: node
   linkType: hard
 
+"ajv-formats@npm:^2.1.1":
+  version: 2.1.1
+  resolution: "ajv-formats@npm:2.1.1"
+  dependencies:
+    ajv: ^8.0.0
+  peerDependencies:
+    ajv: ^8.0.0
+  peerDependenciesMeta:
+    ajv:
+      optional: true
+  checksum: 4a287d937f1ebaad4683249a4c40c0fa3beed30d9ddc0adba04859026a622da0d317851316ea64b3680dc60f5c3c708105ddd5d5db8fe595d9d0207fd19f90b7
+  languageName: node
+  linkType: hard
+
 "ajv-keywords@npm:^3.5.2":
   version: 3.5.2
   resolution: "ajv-keywords@npm:3.5.2"
@@ -3197,7 +3033,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"ajv@npm:8.11.2":
+"ajv@npm:8.11.2, ajv@npm:^8.0.0, ajv@npm:^8.10.0, ajv@npm:^8.11.0":
   version: 8.11.2
   resolution: "ajv@npm:8.11.2"
   dependencies:
@@ -3358,13 +3194,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"append-field@npm:^1.0.0":
-  version: 1.0.0
-  resolution: "append-field@npm:1.0.0"
-  checksum: 482ba08acc0ecef00fe7da6bf2f8e48359a9905ee1af525f3120c9260c02e91eedf0579b59d898e8d8455b6c199e340bc0a2fd4b9e02adaa29a8a86c722b37f9
-  languageName: node
-  linkType: hard
-
 "aproba@npm:^1.0.3 || ^2.0.0":
   version: 2.0.0
   resolution: "aproba@npm:2.0.0"
@@ -3691,6 +3520,13 @@ __metadata:
   languageName: node
   linkType: hard
 
+"atomic-sleep@npm:^1.0.0":
+  version: 1.0.0
+  resolution: "atomic-sleep@npm:1.0.0"
+  checksum: b95275afb2f80732f22f43a60178430c468906a415a7ff18bcd0feeebc8eec3930b51250aeda91a476062a90e07132b43a1794e8d8ffcf9b650e8139be75fa36
+  languageName: node
+  linkType: hard
+
 "autobind-decorator@npm:2.4.0, autobind-decorator@npm:^2.4.0":
   version: 2.4.0
   resolution: "autobind-decorator@npm:2.4.0"
@@ -3735,6 +3571,17 @@ __metadata:
   languageName: node
   linkType: hard
 
+"avvio@npm:^8.2.0":
+  version: 8.2.0
+  resolution: "avvio@npm:8.2.0"
+  dependencies:
+    archy: ^1.0.0
+    debug: ^4.0.0
+    fastq: ^1.6.1
+  checksum: bbd06eeb1f9ef428dbc32a32e06c350a7b320f60348698fd234145a4100f3688ce5d0999b966eb6ca70f9511d0c35fed5ef4651d276715e7e3e94a2d465cb56d
+  languageName: node
+  linkType: hard
+
 "aws-sdk@npm:2.1262.0":
   version: 2.1262.0
   resolution: "aws-sdk@npm:2.1262.0"
@@ -3891,14 +3738,12 @@ __metadata:
   version: 0.0.0-use.local
   resolution: "backend@workspace:packages/backend"
   dependencies:
-    "@bull-board/api": 4.3.1
-    "@bull-board/koa": 4.3.1
-    "@bull-board/ui": 4.3.1
     "@discordapp/twemoji": 14.0.2
-    "@elastic/elasticsearch": 7.17.0
-    "@koa/cors": 3.3.0
-    "@koa/multer": 3.0.0
-    "@koa/router": 9.0.1
+    "@fastify/accepts": 4.0.1
+    "@fastify/cors": 8.2.0
+    "@fastify/multipart": 7.3.0
+    "@fastify/static": 6.5.0
+    "@fastify/view": 7.1.2
     "@nestjs/common": 9.2.0
     "@nestjs/core": 9.2.0
     "@nestjs/testing": 9.2.0
@@ -3910,6 +3755,7 @@ __metadata:
     "@syuilo/aiscript": 0.11.1
     "@tensorflow/tfjs": ^4.1.0
     "@tensorflow/tfjs-node": 4.1.0
+    "@types/accepts": 1.3.5
     "@types/archiver": 5.3.1
     "@types/bcryptjs": 2.4.2
     "@types/bull": 4.10.0
@@ -3921,17 +3767,6 @@ __metadata:
     "@types/jsdom": 20.0.1
     "@types/jsonld": 1.5.8
     "@types/jsrsasign": 10.5.4
-    "@types/koa": 2.13.5
-    "@types/koa-bodyparser": 4.3.8
-    "@types/koa-cors": 0.0.2
-    "@types/koa-favicon": 2.0.21
-    "@types/koa-logger": 3.1.2
-    "@types/koa-mount": 4.0.1
-    "@types/koa-send": 4.1.3
-    "@types/koa-views": 7.0.0
-    "@types/koa__cors": 3.3.0
-    "@types/koa__multer": 2.0.4
-    "@types/koa__router": 8.0.11
     "@types/mime-types": 2.1.1
     "@types/node": 18.11.9
     "@types/node-fetch": 3.0.3
@@ -3954,11 +3789,13 @@ __metadata:
     "@types/tmp": 0.2.3
     "@types/unzipper": 0.10.5
     "@types/uuid": 8.3.4
+    "@types/vary": 1.1.0
     "@types/web-push": 3.3.2
     "@types/websocket": 1.0.5
     "@types/ws": 8.5.3
     "@typescript-eslint/eslint-plugin": 5.45.0
     "@typescript-eslint/parser": 5.45.0
+    accepts: ^1.3.8
     ajv: 8.11.2
     archiver: 5.3.1
     autobind-decorator: 2.4.0
@@ -3982,6 +3819,7 @@ __metadata:
     eslint: 8.28.0
     eslint-plugin-import: 2.26.0
     execa: 6.1.0
+    fastify: 4.10.0
     feed: 4.2.2
     file-type: 18.0.0
     fluent-ffmpeg: 2.1.2
@@ -3999,20 +3837,10 @@ __metadata:
     json5-loader: 4.0.1
     jsonld: 8.1.0
     jsrsasign: 10.6.1
-    koa: 2.13.4
-    koa-bodyparser: 4.3.0
-    koa-favicon: 2.1.0
-    koa-json-body: 5.3.0
-    koa-logger: 3.2.1
-    koa-mount: 4.0.0
-    koa-send: 5.0.1
-    koa-slow: 2.1.0
-    koa-views: 7.0.2
     mfm-js: 0.23.0
     mime-types: 2.1.35
     misskey-js: 0.0.14
     ms: 3.0.0-canary.1
-    multer: 1.4.4
     nested-property: 4.0.0
     node-fetch: 3.3.0
     nodemailer: 6.8.0
@@ -4060,6 +3888,7 @@ __metadata:
     ulid: 2.3.0
     unzipper: 0.10.11
     uuid: 9.0.0
+    vary: 1.1.2
     web-push: 3.5.0
     websocket: 1.0.34
     ws: 8.11.0
@@ -4430,16 +4259,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"busboy@npm:^0.2.11":
-  version: 0.2.14
-  resolution: "busboy@npm:0.2.14"
-  dependencies:
-    dicer: 0.2.5
-    readable-stream: 1.1.x
-  checksum: 9df9fca6d96dab9edd03f568bde31f215794e6fabd73c75d2b39a4be2e8b73a45121d987dea5db881f3fb499737c261b372106fe72d08b8db92afaed8d751165
-  languageName: node
-  linkType: hard
-
 "busboy@npm:^1.6.0":
   version: 1.6.0
   resolution: "busboy@npm:1.6.0"
@@ -4449,13 +4268,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"bytes@npm:3.1.2, bytes@npm:^3.1.0":
-  version: 3.1.2
-  resolution: "bytes@npm:3.1.2"
-  checksum: e4bcd3948d289c5127591fbedf10c0b639ccbf00243504e4e127374a15c3bc8eed0d28d4aaab08ff6f1cf2abc0cce6ba3085ed32f4f90e82a5683ce0014e1b6e
-  languageName: node
-  linkType: hard
-
 "cacache@npm:^16.1.0":
   version: 16.1.3
   resolution: "cacache@npm:16.1.3"
@@ -4702,7 +4514,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"chalk@npm:^2.0.0, chalk@npm:^2.4.2":
+"chalk@npm:^2.0.0":
   version: 2.4.2
   resolution: "chalk@npm:2.4.2"
   dependencies:
@@ -4713,7 +4525,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"chalk@npm:^4.0.0, chalk@npm:^4.0.2, chalk@npm:^4.1.0, chalk@npm:^4.1.2":
+"chalk@npm:^4.0.0, chalk@npm:^4.1.0, chalk@npm:^4.1.2":
   version: 4.1.2
   resolution: "chalk@npm:4.1.2"
   dependencies:
@@ -5118,30 +4930,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"co-body@npm:^5.0.0":
-  version: 5.2.0
-  resolution: "co-body@npm:5.2.0"
-  dependencies:
-    inflation: ^2.0.0
-    qs: ^6.4.0
-    raw-body: ^2.2.0
-    type-is: ^1.6.14
-  checksum: 48e1ffe00b8717b68154a939fa19f36d75aa66bba627f2977f28d11b732da56bdda445acda7053f7a85dfbac8a09a8aa257bceedaff7b6467cb25ab08ada9c8d
-  languageName: node
-  linkType: hard
-
-"co-body@npm:^6.0.0":
-  version: 6.1.0
-  resolution: "co-body@npm:6.1.0"
-  dependencies:
-    inflation: ^2.0.0
-    qs: ^6.5.2
-    raw-body: ^2.3.3
-    type-is: ^1.6.16
-  checksum: d0a78831a6651f2085fce16b0ecdc49f45fb5baf4f94148c2f499e7ec89d188205362548b9c500eae15a819360cfda208079e68a72c204cf66ca3ffa2fc0f57e
-  languageName: node
-  linkType: hard
-
 "co@npm:^4.6.0":
   version: 4.6.0
   resolution: "co@npm:4.6.0"
@@ -5315,7 +5103,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"commander@npm:^2.19.0, commander@npm:^2.20.0":
+"commander@npm:^2.20.0":
   version: 2.20.3
   resolution: "commander@npm:2.20.3"
   checksum: ab8c07884e42c3a8dbc5dd9592c606176c7eb5c1ca5ff274bcf907039b2c41de3626f684ea75ccf4d361ba004bbaff1f577d5384c155f3871e456bdf27becf9e
@@ -5383,7 +5171,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"concat-stream@npm:^1.5.2, concat-stream@npm:^1.6.0":
+"concat-stream@npm:^1.6.0":
   version: 1.6.2
   resolution: "concat-stream@npm:1.6.2"
   dependencies:
@@ -5395,27 +5183,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"condense-newlines@npm:^0.2.1":
-  version: 0.2.1
-  resolution: "condense-newlines@npm:0.2.1"
-  dependencies:
-    extend-shallow: ^2.0.1
-    is-whitespace: ^0.3.0
-    kind-of: ^3.0.2
-  checksum: 3c20ff6ee88b5d2e81c122f33b5ba5d6976cdf86d83527fadea308b3020ed70af7ed98c2e2d94d36f27fcd723a7a477941c19575e0d2c8db6afc4aac6926a54e
-  languageName: node
-  linkType: hard
-
-"config-chain@npm:^1.1.13":
-  version: 1.1.13
-  resolution: "config-chain@npm:1.1.13"
-  dependencies:
-    ini: ^1.3.4
-    proto-list: ~1.2.1
-  checksum: 828137a28e7c2fc4b7fb229bd0cd6c1397bcf83434de54347e608154008f411749041ee392cbe42fab6307e02de4c12480260bf769b7d44b778fdea3839eafab
-  languageName: node
-  linkType: hard
-
 "consola@npm:^2.15.0":
   version: 2.15.3
   resolution: "consola@npm:2.15.3"
@@ -5430,15 +5197,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"consolidate@npm:^0.16.0":
-  version: 0.16.0
-  resolution: "consolidate@npm:0.16.0"
-  dependencies:
-    bluebird: ^3.7.2
-  checksum: f17164ffb2c4f79b4cbf685f1c76a49f59d329a40954b436425498861dc137b46fe821b2aadafa2dcfeb7eebd46846f35bd2c36b4a704d38521b4210a22a7515
-  languageName: node
-  linkType: hard
-
 "constantinople@npm:^4.0.1":
   version: 4.0.1
   resolution: "constantinople@npm:4.0.1"
@@ -5449,7 +5207,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"content-disposition@npm:0.5.4, content-disposition@npm:~0.5.2":
+"content-disposition@npm:0.5.4, content-disposition@npm:^0.5.3, content-disposition@npm:~0.5.2":
   version: 0.5.4
   resolution: "content-disposition@npm:0.5.4"
   dependencies:
@@ -5479,6 +5237,13 @@ __metadata:
   languageName: node
   linkType: hard
 
+"cookie@npm:^0.5.0":
+  version: 0.5.0
+  resolution: "cookie@npm:0.5.0"
+  checksum: 1f4bd2ca5765f8c9689a7e8954183f5332139eb72b6ff783d8947032ec1fdf43109852c178e21a953a30c0dd42257828185be01b49d1eb1a67fd054ca588a180
+  languageName: node
+  linkType: hard
+
 "cookies@npm:~0.8.0":
   version: 0.8.0
   resolution: "cookies@npm:0.8.0"
@@ -5506,13 +5271,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"copy-to@npm:^2.0.1":
-  version: 2.0.1
-  resolution: "copy-to@npm:2.0.1"
-  checksum: 05ea12875bdc96ae053a3b30148e9d992026035ff2bfcc0b615e8d49d1cf8fc3d1f40843f9a4b7b1b6d9118eeebcba31e621076d7de525828aa9c07d22a81dab
-  languageName: node
-  linkType: hard
-
 "core-js@npm:3":
   version: 3.26.1
   resolution: "core-js@npm:3.26.1"
@@ -5838,7 +5596,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"debug@npm:2, debug@npm:^2.2.0, debug@npm:^2.3.3, debug@npm:^2.5.2, debug@npm:^2.6.9":
+"debug@npm:2, debug@npm:2.6.9, debug@npm:^2.2.0, debug@npm:^2.3.3, debug@npm:^2.5.2, debug@npm:^2.6.9":
   version: 2.6.9
   resolution: "debug@npm:2.6.9"
   dependencies:
@@ -5847,7 +5605,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"debug@npm:4, debug@npm:^4.0.1, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.3, debug@npm:^4.3.4":
+"debug@npm:4, debug@npm:^4.0.0, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.3, debug@npm:^4.3.4":
   version: 4.3.4
   resolution: "debug@npm:4.3.4"
   dependencies:
@@ -6086,7 +5844,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"destroy@npm:^1.0.4":
+"destroy@npm:1.2.0, destroy@npm:^1.0.4":
   version: 1.2.0
   resolution: "destroy@npm:1.2.0"
   checksum: 0acb300b7478a08b92d810ab229d5afe0d2f4399272045ab22affa0d99dbaf12637659411530a6fcd597a9bdac718fc94373a61a95b4651bbc7b83684a565e38
@@ -6130,16 +5888,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"dicer@npm:0.2.5":
-  version: 0.2.5
-  resolution: "dicer@npm:0.2.5"
-  dependencies:
-    readable-stream: 1.1.x
-    streamsearch: 0.1.2
-  checksum: a6f0ce9ac5099c7ffeaec7398d711eea1dd803eb99036d0f05342e9ed46a4235a5ed0ea01ad5d6c785fdb0aae6d61d2722e6e64f9fabdfe39885f7f52eb635ee
-  languageName: node
-  linkType: hard
-
 "diff-sequences@npm:^29.3.1":
   version: 29.3.1
   resolution: "diff-sequences@npm:29.3.1"
@@ -6369,20 +6117,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"editorconfig@npm:^0.15.3":
-  version: 0.15.3
-  resolution: "editorconfig@npm:0.15.3"
-  dependencies:
-    commander: ^2.19.0
-    lru-cache: ^4.1.5
-    semver: ^5.6.0
-    sigmund: ^1.0.1
-  bin:
-    editorconfig: bin/editorconfig
-  checksum: a94afeda19f12a4bcc4a573f0858df13dd3a2d1a3268cc0f17a6326ebe7ddd6cb0c026f8e4e73c17d34f3892bf6f8b561512d9841e70063f61da71b4c57dc5f0
-  languageName: node
-  linkType: hard
-
 "ee-first@npm:1.1.1":
   version: 1.1.1
   resolution: "ee-first@npm:1.1.1"
@@ -6390,17 +6124,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"ejs@npm:*, ejs@npm:^3.1.7":
-  version: 3.1.8
-  resolution: "ejs@npm:3.1.8"
-  dependencies:
-    jake: ^10.8.5
-  bin:
-    ejs: bin/cli.js
-  checksum: 1d40d198ad52e315ccf37e577bdec06e24eefdc4e3c27aafa47751a03a0c7f0ec4310254c9277a5f14763c3cd4bbacce27497332b2d87c74232b9b1defef8efc
-  languageName: node
-  linkType: hard
-
 "electron-to-chromium@npm:^1.2.7, electron-to-chromium@npm:^1.4.251":
   version: 1.4.284
   resolution: "electron-to-chromium@npm:1.4.284"
@@ -6436,7 +6159,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"encodeurl@npm:^1.0.2":
+"encodeurl@npm:^1.0.2, encodeurl@npm:~1.0.2":
   version: 1.0.2
   resolution: "encodeurl@npm:1.0.2"
   checksum: e50e3d508cdd9c4565ba72d2012e65038e5d71bdc9198cb125beb6237b5b1ade6c0d343998da9e170fb2eae52c1bed37d4d6d98a46ea423a0cddbed5ac3f780c
@@ -6452,7 +6175,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"end-of-stream@npm:^1.0.0, end-of-stream@npm:^1.1.0, end-of-stream@npm:^1.4.1":
+"end-of-stream@npm:^1.0.0, end-of-stream@npm:^1.1.0, end-of-stream@npm:^1.4.1, end-of-stream@npm:^1.4.4":
   version: 1.4.4
   resolution: "end-of-stream@npm:1.4.4"
   dependencies:
@@ -7074,7 +6797,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"escape-html@npm:^1.0.3":
+"escape-html@npm:^1.0.3, escape-html@npm:~1.0.3":
   version: 1.0.3
   resolution: "escape-html@npm:1.0.3"
   checksum: 6213ca9ae00d0ab8bccb6d8d4e0a98e76237b2410302cf7df70aaa6591d509a2a37ce8998008cbecae8fc8ffaadf3fb0229535e6a145f3ce0b211d060decbb24
@@ -7361,6 +7084,13 @@ __metadata:
   languageName: node
   linkType: hard
 
+"etag@npm:~1.8.1":
+  version: 1.8.1
+  resolution: "etag@npm:1.8.1"
+  checksum: 571aeb3dbe0f2bbd4e4fadbdb44f325fc75335cd5f6f6b6a091e6a06a9f25ed5392f0863c5442acb0646787446e816f13cbfc6edce5b07658541dff573cab1ff
+  languageName: node
+  linkType: hard
+
 "event-stream@npm:=3.3.4":
   version: 3.3.4
   resolution: "event-stream@npm:3.3.4"
@@ -7411,6 +7141,13 @@ __metadata:
   languageName: node
   linkType: hard
 
+"events@npm:^3.3.0":
+  version: 3.3.0
+  resolution: "events@npm:3.3.0"
+  checksum: f6f487ad2198aa41d878fa31452f1a3c00958f46e9019286ff4787c84aac329332ab45c9cdc8c445928fc6d7ded294b9e005a7fce9426488518017831b272780
+  languageName: node
+  linkType: hard
+
 "execa@npm:4.1.0":
   version: 4.1.0
   resolution: "execa@npm:4.1.0"
@@ -7616,6 +7353,13 @@ __metadata:
   languageName: node
   linkType: hard
 
+"fast-decode-uri-component@npm:^1.0.1":
+  version: 1.0.1
+  resolution: "fast-decode-uri-component@npm:1.0.1"
+  checksum: 427a48fe0907e76f0e9a2c228e253b4d8a8ab21d130ee9e4bb8339c5ba4086235cf9576831f7b20955a752eae4b525a177ff9d5825dd8d416e7726939194fbee
+  languageName: node
+  linkType: hard
+
 "fast-deep-equal@npm:^3.1.1, fast-deep-equal@npm:^3.1.3":
   version: 3.1.3
   resolution: "fast-deep-equal@npm:3.1.3"
@@ -7643,6 +7387,20 @@ __metadata:
   languageName: node
   linkType: hard
 
+"fast-json-stringify@npm:^5.0.0":
+  version: 5.4.1
+  resolution: "fast-json-stringify@npm:5.4.1"
+  dependencies:
+    "@fastify/deepmerge": ^1.0.0
+    ajv: ^8.10.0
+    ajv-formats: ^2.1.1
+    fast-deep-equal: ^3.1.3
+    fast-uri: ^2.1.0
+    rfdc: ^1.2.0
+  checksum: 62efefaf135ff03d810fb362adca1d3471787e4e17ef10e34c8e1d61d361c09736091b1948df2cb408e6b05f18c10985e89bcdd9c08f8f5ba21e148e52a9c5fc
+  languageName: node
+  linkType: hard
+
 "fast-levenshtein@npm:^1.0.0":
   version: 1.1.4
   resolution: "fast-levenshtein@npm:1.1.4"
@@ -7657,6 +7415,22 @@ __metadata:
   languageName: node
   linkType: hard
 
+"fast-querystring@npm:^1.0.0":
+  version: 1.0.0
+  resolution: "fast-querystring@npm:1.0.0"
+  dependencies:
+    fast-decode-uri-component: ^1.0.1
+  checksum: 5f70df27d02fcf86ea2baa16ea59e0da8bbd891e3a97aa1e95b1c0c64d5445aeab3bde5ce3e603b21d48c87db70a458febf05150a9dbe7c099aced5f123b3ffd
+  languageName: node
+  linkType: hard
+
+"fast-redact@npm:^3.1.1":
+  version: 3.1.2
+  resolution: "fast-redact@npm:3.1.2"
+  checksum: a30eb6b6830333ab213e0def55f46453ca777544dbd3a883016cb590a0eeb95e6fdf546553c1a13d509896bfba889b789991160a6d0996ceb19fce0a02e8b753
+  languageName: node
+  linkType: hard
+
 "fast-safe-stringify@npm:2.1.1":
   version: 2.1.1
   resolution: "fast-safe-stringify@npm:2.1.1"
@@ -7664,6 +7438,13 @@ __metadata:
   languageName: node
   linkType: hard
 
+"fast-uri@npm:^2.0.0, fast-uri@npm:^2.1.0":
+  version: 2.1.0
+  resolution: "fast-uri@npm:2.1.0"
+  checksum: 60ecece5ab05515729ec04d1732ee68bd4429cab8c06ebf8db512a094a0077ddc5af6a27c75922875bc9e13b58e947832242cdcb2cb23c51dc753412222dca83
+  languageName: node
+  linkType: hard
+
 "fast-xml-parser@npm:^3.19.0":
   version: 3.21.1
   resolution: "fast-xml-parser@npm:3.21.1"
@@ -7675,7 +7456,36 @@ __metadata:
   languageName: node
   linkType: hard
 
-"fastq@npm:^1.6.0":
+"fastify-plugin@npm:^4.0.0":
+  version: 4.3.0
+  resolution: "fastify-plugin@npm:4.3.0"
+  checksum: f4831ca6de3db276f6e5c9ae172c175631be07b24b91e1de17d0cd11c1bd29fe7f671deec591a4dfd26169d3de4f9d9ca385d0e7bddccf38c6d21b5764f8b77d
+  languageName: node
+  linkType: hard
+
+"fastify@npm:4.10.0":
+  version: 4.10.0
+  resolution: "fastify@npm:4.10.0"
+  dependencies:
+    "@fastify/ajv-compiler": ^3.3.1
+    "@fastify/error": ^3.0.0
+    "@fastify/fast-json-stringify-compiler": ^4.1.0
+    abstract-logging: ^2.0.1
+    avvio: ^8.2.0
+    find-my-way: ^7.3.0
+    light-my-request: ^5.6.1
+    pino: ^8.5.0
+    process-warning: ^2.0.0
+    proxy-addr: ^2.0.7
+    rfdc: ^1.3.0
+    secure-json-parse: ^2.5.0
+    semver: ^7.3.7
+    tiny-lru: ^10.0.0
+  checksum: 68c905a930d1e6d31d70a1e048c39c2d5d3749e00b9653fa7f20156da791d68f7edfa2856e8b3ea898b6e38b0a70b09782555e3f503a77cda49e8fd983a1a0ba
+  languageName: node
+  linkType: hard
+
+"fastq@npm:^1.6.0, fastq@npm:^1.6.1":
   version: 1.13.0
   resolution: "fastq@npm:1.13.0"
   dependencies:
@@ -7750,15 +7560,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"filelist@npm:^1.0.1":
-  version: 1.0.4
-  resolution: "filelist@npm:1.0.4"
-  dependencies:
-    minimatch: ^5.0.1
-  checksum: a303573b0821e17f2d5e9783688ab6fbfce5d52aaac842790ae85e704a6f5e4e3538660a63183d6453834dedf1e0f19a9dadcebfa3e926c72397694ea11f5160
-  languageName: node
-  linkType: hard
-
 "fill-range@npm:^4.0.0":
   version: 4.0.0
   resolution: "fill-range@npm:4.0.0"
@@ -7780,6 +7581,17 @@ __metadata:
   languageName: node
   linkType: hard
 
+"find-my-way@npm:^7.3.0":
+  version: 7.3.1
+  resolution: "find-my-way@npm:7.3.1"
+  dependencies:
+    fast-deep-equal: ^3.1.3
+    fast-querystring: ^1.0.0
+    safe-regex2: ^2.0.0
+  checksum: eec65665c34fbfeb323a52989de51b106485ec0d6182996fc70d42570a73f88b9637572bb8ae89332532da9ca856615e195768116aeede75d73b929b9534bf7a
+  languageName: node
+  linkType: hard
+
 "find-up@npm:^1.0.0":
   version: 1.1.2
   resolution: "find-up@npm:1.1.2"
@@ -7989,6 +7801,13 @@ __metadata:
   languageName: node
   linkType: hard
 
+"forwarded@npm:0.2.0":
+  version: 0.2.0
+  resolution: "forwarded@npm:0.2.0"
+  checksum: fd27e2394d8887ebd16a66ffc889dc983fbbd797d5d3f01087c020283c0f019a7d05ee85669383d8e0d216b116d720fc0cef2f6e9b7eb9f4c90c6e0bc7fd28e6
+  languageName: node
+  linkType: hard
+
 "fragment-cache@npm:^0.2.1":
   version: 0.2.1
   resolution: "fragment-cache@npm:0.2.1"
@@ -7998,7 +7817,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"fresh@npm:~0.5.2":
+"fresh@npm:0.5.2, fresh@npm:~0.5.2":
   version: 0.5.2
   resolution: "fresh@npm:0.5.2"
   checksum: 13ea8b08f91e669a64e3ba3a20eb79d7ca5379a81f1ff7f4310d54e2320645503cc0c78daedc93dfb6191287295f6479544a649c64d8e41a1c0fb0c221552346
@@ -8213,15 +8032,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"get-paths@npm:0.0.7":
-  version: 0.0.7
-  resolution: "get-paths@npm:0.0.7"
-  dependencies:
-    pify: ^4.0.1
-  checksum: a17edf61fb9934b8e58a7d8ce0d9702040b7020dda86e67ce088db865c21cb230f490f25f38064cebeb2c367abc2bf39a75db6acdfddf01da63a699a47f8aba4
-  languageName: node
-  linkType: hard
-
 "get-pixels-frame-info-update@npm:3.3.2":
   version: 3.3.2
   resolution: "get-pixels-frame-info-update@npm:3.3.2"
@@ -8390,7 +8200,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"glob@npm:^8.0.1, glob@npm:^8.0.3":
+"glob@npm:^8.0.1":
   version: 8.0.3
   resolution: "glob@npm:8.0.3"
   dependencies:
@@ -8780,6 +8590,20 @@ __metadata:
   languageName: node
   linkType: hard
 
+"hashlru@npm:^2.3.0":
+  version: 2.3.0
+  resolution: "hashlru@npm:2.3.0"
+  checksum: 38b3559e6fb9d19fa731edc52d8d7e72cd378f708dcb01cecd4a6ba0c52f06d7d06d6277249f5c43d9915d8dda9be31adad768a379eef188db213c3f2b09278d
+  languageName: node
+  linkType: hard
+
+"hexoid@npm:^1.0.0":
+  version: 1.0.0
+  resolution: "hexoid@npm:1.0.0"
+  checksum: 27a148ca76a2358287f40445870116baaff4a0ed0acc99900bf167f0f708ffd82e044ff55e9949c71963852b580fc024146d3ac6d5d76b508b78d927fa48ae2d
+  languageName: node
+  linkType: hard
+
 "highlight.js@npm:^10.7.1":
   version: 10.7.3
   resolution: "highlight.js@npm:10.7.3"
@@ -8810,13 +8634,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"hpagent@npm:^0.1.1":
-  version: 0.1.2
-  resolution: "hpagent@npm:0.1.2"
-  checksum: 1918518ab937d9fa615a47b94489e23662547bc1edf27069ee9bf40bfefb94da65eb142b6f42336b4b0752fce87f66c284d92b97340fd2a90b24aa3616b5450d
-  languageName: node
-  linkType: hard
-
 "html-comment-regex@npm:^1.1.0":
   version: 1.1.2
   resolution: "html-comment-regex@npm:1.1.2"
@@ -8903,7 +8720,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"http-errors@npm:^1.6.3, http-errors@npm:^1.7.3, http-errors@npm:~1.8.0":
+"http-errors@npm:^1.6.3, http-errors@npm:~1.8.0":
   version: 1.8.1
   resolution: "http-errors@npm:1.8.1"
   dependencies:
@@ -8916,18 +8733,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"http-errors@npm:~1.6.2":
-  version: 1.6.3
-  resolution: "http-errors@npm:1.6.3"
-  dependencies:
-    depd: ~1.1.2
-    inherits: 2.0.3
-    setprototypeof: 1.1.0
-    statuses: ">= 1.4.0 < 2"
-  checksum: a9654ee027e3d5de305a56db1d1461f25709ac23267c6dc28cdab8323e3f96caa58a9a6a5e93ac15d7285cee0c2f019378c3ada9026e7fe19c872d695f27de7c
-  languageName: node
-  linkType: hard
-
 "http-proxy-agent@npm:^5.0.0":
   version: 5.0.0
   resolution: "http-proxy-agent@npm:5.0.0"
@@ -9040,22 +8845,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"humanize-number@npm:0.0.2":
-  version: 0.0.2
-  resolution: "humanize-number@npm:0.0.2"
-  checksum: 9c98c9d06b0f3d801960be3957199232a5df52377e2502acae92e4f71de633fa62c315a83f24bf96bef76f47b2e3e0e1e4f4157c891e27074fd3272cad6724bb
-  languageName: node
-  linkType: hard
-
-"iconv-lite@npm:0.4.24, iconv-lite@npm:^0.4.4":
-  version: 0.4.24
-  resolution: "iconv-lite@npm:0.4.24"
-  dependencies:
-    safer-buffer: ">= 2.1.2 < 3"
-  checksum: bd9f120f5a5b306f0bc0b9ae1edeb1577161503f5f8252a20f1a9e56ef8775c9959fd01c55f2d3a39d9a8abaf3e30c1abeb1895f367dcbbe0a8fd1c9ca01c4f6
-  languageName: node
-  linkType: hard
-
 "iconv-lite@npm:0.6.3, iconv-lite@npm:^0.6.2":
   version: 0.6.3
   resolution: "iconv-lite@npm:0.6.3"
@@ -9065,6 +8854,15 @@ __metadata:
   languageName: node
   linkType: hard
 
+"iconv-lite@npm:^0.4.4":
+  version: 0.4.24
+  resolution: "iconv-lite@npm:0.4.24"
+  dependencies:
+    safer-buffer: ">= 2.1.2 < 3"
+  checksum: bd9f120f5a5b306f0bc0b9ae1edeb1577161503f5f8252a20f1a9e56ef8775c9959fd01c55f2d3a39d9a8abaf3e30c1abeb1895f367dcbbe0a8fd1c9ca01c4f6
+  languageName: node
+  linkType: hard
+
 "idb-keyval@npm:6.2.0, idb-keyval@npm:^6.1.0":
   version: 6.2.0
   resolution: "idb-keyval@npm:6.2.0"
@@ -9152,13 +8950,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"inflation@npm:^2.0.0":
-  version: 2.0.0
-  resolution: "inflation@npm:2.0.0"
-  checksum: a0494871b12275afdef9e2710ee1af1e0fc642b04613a9be69c05ef8b5e9627f3bd7d358a937fa47aa20235ee7313a4f30255048533add0ad4918beb918a586e
-  languageName: node
-  linkType: hard
-
 "inflight@npm:^1.0.4":
   version: 1.0.6
   resolution: "inflight@npm:1.0.6"
@@ -9176,13 +8967,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"inherits@npm:2.0.3":
-  version: 2.0.3
-  resolution: "inherits@npm:2.0.3"
-  checksum: 78cb8d7d850d20a5e9a7f3620db31483aa00ad5f722ce03a55b110e5a723539b3716a3b463e2b96ce3fe286f33afc7c131fa2f91407528ba80cea98a7545d4c0
-  languageName: node
-  linkType: hard
-
 "ini@npm:2.0.0":
   version: 2.0.0
   resolution: "ini@npm:2.0.0"
@@ -9323,6 +9107,13 @@ __metadata:
   languageName: node
   linkType: hard
 
+"ipaddr.js@npm:1.9.1":
+  version: 1.9.1
+  resolution: "ipaddr.js@npm:1.9.1"
+  checksum: f88d3825981486f5a1942414c8d77dd6674dd71c065adcfa46f578d677edcb99fda25af42675cb59db492fdf427b34a5abfcde3982da11a8fd83a500b41cfe77
+  languageName: node
+  linkType: hard
+
 "ipaddr.js@npm:^2.0.1":
   version: 2.0.1
   resolution: "ipaddr.js@npm:2.0.1"
@@ -9836,13 +9627,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"is-whitespace@npm:^0.3.0":
-  version: 0.3.0
-  resolution: "is-whitespace@npm:0.3.0"
-  checksum: dac8fc9a9b797afeef703f625269601715552883790d1385d6bb27dd04ffdafd5fddca8f2d85ee96913850211595da2ba483dac1f166829c4078fb58ce815140
-  languageName: node
-  linkType: hard
-
 "is-windows@npm:^1.0.1, is-windows@npm:^1.0.2":
   version: 1.0.2
   resolution: "is-windows@npm:1.0.2"
@@ -9963,20 +9747,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"jake@npm:^10.8.5":
-  version: 10.8.5
-  resolution: "jake@npm:10.8.5"
-  dependencies:
-    async: ^3.2.3
-    chalk: ^4.0.2
-    filelist: ^1.0.1
-    minimatch: ^3.0.4
-  bin:
-    jake: ./bin/cli.js
-  checksum: 56c913ecf5a8d74325d0af9bc17a233bad50977438d44864d925bb6c45c946e0fee8c4c1f5fe2225471ef40df5222e943047982717ebff0d624770564d3c46ba
-  languageName: node
-  linkType: hard
-
 "jest-changed-files@npm:^29.2.0":
   version: 29.2.0
   resolution: "jest-changed-files@npm:29.2.0"
@@ -10460,22 +10230,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"js-beautify@npm:^1.6.12":
-  version: 1.14.7
-  resolution: "js-beautify@npm:1.14.7"
-  dependencies:
-    config-chain: ^1.1.13
-    editorconfig: ^0.15.3
-    glob: ^8.0.3
-    nopt: ^6.0.0
-  bin:
-    css-beautify: js/bin/css-beautify.js
-    html-beautify: js/bin/html-beautify.js
-    js-beautify: js/bin/js-beautify.js
-  checksum: 1950d0d3f05f8ad06b73eb77b9aac602d00b24eab7d8a6d8ea0b1841ab9c730acecd5a6f3926e360dce7a2583481bc77caf6d024490a58fa9897cbbbdfc35984
-  languageName: node
-  linkType: hard
-
 "js-levenshtein@npm:^1.1.6":
   version: 1.1.6
   resolution: "js-levenshtein@npm:1.1.6"
@@ -10884,16 +10638,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"koa-bodyparser@npm:4.3.0":
-  version: 4.3.0
-  resolution: "koa-bodyparser@npm:4.3.0"
-  dependencies:
-    co-body: ^6.0.0
-    copy-to: ^2.0.1
-  checksum: c227fe0fb5a55b98fc91d865e80229b60178d216d53b732b07833eb38f48a7ed6aa768a083bc06e359db33298547e9a65842fbe9d3f0fdaf5149fe0becafc88f
-  languageName: node
-  linkType: hard
-
 "koa-compose@npm:^4.1.0":
   version: 4.1.0
   resolution: "koa-compose@npm:4.1.0"
@@ -10911,131 +10655,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"koa-favicon@npm:2.1.0":
-  version: 2.1.0
-  resolution: "koa-favicon@npm:2.1.0"
-  dependencies:
-    mz: ^2.7.0
-  checksum: 024051a0be3560a77e65651ad87690d432a28bed3c4dd24cbcbe3249a38d6cc0c80613d37b45107c8b709cb01fecf8723e77d50cbb2ce67c8451fa29891d228f
-  languageName: node
-  linkType: hard
-
-"koa-json-body@npm:5.3.0":
-  version: 5.3.0
-  resolution: "koa-json-body@npm:5.3.0"
-  dependencies:
-    co-body: ^5.0.0
-  checksum: b3ae5f304cbb6f8f6a4a2ba36047d1ea6fe32005e7665d795802eb4c3dd1f341a0b8e61087fffc6bd2da6f554d71b06b047cbb3669b1eea814285f75d9bdd736
-  languageName: node
-  linkType: hard
-
-"koa-logger@npm:3.2.1":
-  version: 3.2.1
-  resolution: "koa-logger@npm:3.2.1"
-  dependencies:
-    bytes: ^3.1.0
-    chalk: ^2.4.2
-    humanize-number: 0.0.2
-    passthrough-counter: ^1.0.0
-  checksum: b29ba25eb433452bfda48e51acd5d206128411966acc09bb13ce3a0cec9192f78bb27e23efd615d0e7f46eeb2588ee8d2541d72665a4aa18d27a177e78dca909
-  languageName: node
-  linkType: hard
-
-"koa-mount@npm:4.0.0, koa-mount@npm:^4.0.0":
-  version: 4.0.0
-  resolution: "koa-mount@npm:4.0.0"
-  dependencies:
-    debug: ^4.0.1
-    koa-compose: ^4.1.0
-  checksum: c7e8c5cca4d2ccc4742e63c81b86b44f0290075148897b5d633acdd137e90f554c60c232fbc62e843eaedb913b67c5a49367c1142e290b8cfd9c28eb4a0480ec
-  languageName: node
-  linkType: hard
-
-"koa-router@npm:^10.0.0":
-  version: 10.1.1
-  resolution: "koa-router@npm:10.1.1"
-  dependencies:
-    debug: ^4.1.1
-    http-errors: ^1.7.3
-    koa-compose: ^4.1.0
-    methods: ^1.1.2
-    path-to-regexp: ^6.1.0
-  checksum: 65e6cd4a7f8a4d98c665b00ee4c2c05340cb38ca035590ce71c23a25c0a01f6d2434d9a68366d7c218af9c94e5d8e20c7fe9e7f7dfbb98d69b11b5ae3246aaf8
-  languageName: node
-  linkType: hard
-
-"koa-send@npm:5.0.1, koa-send@npm:^5.0.0":
-  version: 5.0.1
-  resolution: "koa-send@npm:5.0.1"
-  dependencies:
-    debug: ^4.1.1
-    http-errors: ^1.7.3
-    resolve-path: ^1.4.0
-  checksum: a9fbaadbe0f50efd157a733df4a1cc2b3b79b0cdf12e67c718641e6038d1792c0bebe40913e6d4ceb707d970301155be3859b98d1ef08b0fd1766f7326b82853
-  languageName: node
-  linkType: hard
-
-"koa-slow@npm:2.1.0":
-  version: 2.1.0
-  resolution: "koa-slow@npm:2.1.0"
-  dependencies:
-    lodash.isregexp: 3.0.5
-    q: 1.4.1
-  checksum: 1b2fa6c709cd4016f5c5c4f45a8bd569910fdfef482c85120f2bbddd5cf274d714b0d231659ac3335d15b03f0debdb71b14f3cc54624921be7d808df7f8ac513
-  languageName: node
-  linkType: hard
-
-"koa-static@npm:^5.0.0":
-  version: 5.0.0
-  resolution: "koa-static@npm:5.0.0"
-  dependencies:
-    debug: ^3.1.0
-    koa-send: ^5.0.0
-  checksum: 8d9b9c4d2b3b13e8818e804245d784099c4b353b55ddd7dbeeb90f27a2e9f5b6f86bd16a4909e337cb89db4d332d9002e6c0f5056caf75749cab62f93c1f0cc5
-  languageName: node
-  linkType: hard
-
-"koa-views@npm:*":
-  version: 8.0.0
-  resolution: "koa-views@npm:8.0.0"
-  dependencies:
-    consolidate: ^0.16.0
-    debug: ^4.1.0
-    get-paths: 0.0.7
-    koa-send: ^5.0.0
-    mz: ^2.4.0
-    pretty: ^2.0.0
-    resolve-path: ^1.4.0
-  peerDependencies:
-    "@types/koa": ^2.13.1
-  peerDependenciesMeta:
-    "@types/koa":
-      optional: true
-  checksum: 6e7e4df04fcab8e5c50a82b38518b7e87491fef1a80f8105e34aaf82a93c0abf051a011d02a8bed8542255724bdb2b83cda7b63e094983c01a5c3927ea08be2c
-  languageName: node
-  linkType: hard
-
-"koa-views@npm:7.0.2, koa-views@npm:^7.0.1":
-  version: 7.0.2
-  resolution: "koa-views@npm:7.0.2"
-  dependencies:
-    consolidate: ^0.16.0
-    debug: ^4.1.0
-    get-paths: 0.0.7
-    koa-send: ^5.0.0
-    mz: ^2.4.0
-    pretty: ^2.0.0
-    resolve-path: ^1.4.0
-  peerDependencies:
-    "@types/koa": ^2.13.1
-  peerDependenciesMeta:
-    "@types/koa":
-      optional: true
-  checksum: e591a131de09cf2676ae0492dabf420015404cd1198092a2aa217118c5e7df5da848f49c658f46af172028a508cecbdb0a81e45c5acf5cf40c2baf7c9d08675e
-  languageName: node
-  linkType: hard
-
-"koa@npm:2.13.4, koa@npm:^2.13.1":
+"koa@npm:2.13.4":
   version: 2.13.4
   resolution: "koa@npm:2.13.4"
   dependencies:
@@ -11176,6 +10796,17 @@ __metadata:
   languageName: node
   linkType: hard
 
+"light-my-request@npm:^5.6.1":
+  version: 5.7.0
+  resolution: "light-my-request@npm:5.7.0"
+  dependencies:
+    cookie: ^0.5.0
+    process-warning: ^2.0.0
+    set-cookie-parser: ^2.4.1
+  checksum: 3da727736c03b89d1eac4b3cebc9de3d8b725a1485fb86f63b450715fa1cb7f049d7df391b831eb9ec352287fd58f1948835c1e1e78444f193eb0a9c90216aed
+  languageName: node
+  linkType: hard
+
 "lines-and-columns@npm:^1.1.6":
   version: 1.2.4
   resolution: "lines-and-columns@npm:1.2.4"
@@ -11323,13 +10954,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"lodash.isregexp@npm:3.0.5":
-  version: 3.0.5
-  resolution: "lodash.isregexp@npm:3.0.5"
-  checksum: 973f4887f003af746bf838267d9d1ea39d912f579cf402cca67049b1e4487daf2a25b10c70e4fc1c7ad97ee3be6d43d38c9839bc9c55c40e94b62dfc60f601c7
-  languageName: node
-  linkType: hard
-
 "lodash.map@npm:^4.4.0":
   version: 4.6.0
   resolution: "lodash.map@npm:4.6.0"
@@ -11457,16 +11081,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"lru-cache@npm:^4.1.5":
-  version: 4.1.5
-  resolution: "lru-cache@npm:4.1.5"
-  dependencies:
-    pseudomap: ^1.0.2
-    yallist: ^2.1.2
-  checksum: 4bb4b58a36cd7dc4dcec74cbe6a8f766a38b7426f1ff59d4cf7d82a2aa9b9565cd1cb98f6ff60ce5cd174524868d7bc9b7b1c294371851356066ca9ac4cf135a
-  languageName: node
-  linkType: hard
-
 "lru-cache@npm:^6.0.0":
   version: 6.0.0
   resolution: "lru-cache@npm:6.0.0"
@@ -11634,13 +11248,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"methods@npm:^1.1.2":
-  version: 1.1.2
-  resolution: "methods@npm:1.1.2"
-  checksum: 0917ff4041fa8e2f2fda5425a955fe16ca411591fbd123c0d722fcf02b73971ed6f764d85f0a6f547ce49ee0221ce2c19a5fa692157931cecb422984f1dcd13a
-  languageName: node
-  linkType: hard
-
 "mfm-js@npm:0.23.0":
   version: 0.23.0
   resolution: "mfm-js@npm:0.23.0"
@@ -11697,6 +11304,15 @@ __metadata:
   languageName: node
   linkType: hard
 
+"mime@npm:1.6.0":
+  version: 1.6.0
+  resolution: "mime@npm:1.6.0"
+  bin:
+    mime: cli.js
+  checksum: fef25e39263e6d207580bdc629f8872a3f9772c923c7f8c7e793175cee22777bbe8bba95e5d509a40aaa292d8974514ce634ae35769faa45f22d17edda5e8557
+  languageName: node
+  linkType: hard
+
 "mimic-fn@npm:^2.1.0":
   version: 2.1.0
   resolution: "mimic-fn@npm:2.1.0"
@@ -11904,7 +11520,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"mkdirp@npm:>=0.5 0, mkdirp@npm:^0.5.4, mkdirp@npm:^0.5.5, mkdirp@npm:~0.5.1":
+"mkdirp@npm:>=0.5 0, mkdirp@npm:^0.5.5, mkdirp@npm:~0.5.1":
   version: 0.5.6
   resolution: "mkdirp@npm:0.5.6"
   dependencies:
@@ -11924,6 +11540,15 @@ __metadata:
   languageName: node
   linkType: hard
 
+"mnemonist@npm:0.39.5":
+  version: 0.39.5
+  resolution: "mnemonist@npm:0.39.5"
+  dependencies:
+    obliterator: ^2.0.1
+  checksum: 6669d687a434226924b2c84ee6eb7ce7d0f83dfc5caad8bcc164c73c0c11fb6d43cbe32636e710f068046f4b40a56c3032532554e93e02640aafc6ca3dd222e6
+  languageName: node
+  linkType: hard
+
 "moment@npm:^2.22.2":
   version: 2.29.4
   resolution: "moment@npm:2.29.4"
@@ -11945,6 +11570,13 @@ __metadata:
   languageName: node
   linkType: hard
 
+"ms@npm:2.1.3, ms@npm:^2.0.0, ms@npm:^2.1.1":
+  version: 2.1.3
+  resolution: "ms@npm:2.1.3"
+  checksum: aa92de608021b242401676e35cfa5aa42dd70cbdc082b916da7fb925c542173e36bce97ea3e804923fe92c0ad991434e4a38327e15a1b5b5f945d66df615ae6d
+  languageName: node
+  linkType: hard
+
 "ms@npm:3.0.0-canary.1":
   version: 3.0.0-canary.1
   resolution: "ms@npm:3.0.0-canary.1"
@@ -11952,13 +11584,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"ms@npm:^2.0.0, ms@npm:^2.1.1, ms@npm:^2.1.3":
-  version: 2.1.3
-  resolution: "ms@npm:2.1.3"
-  checksum: aa92de608021b242401676e35cfa5aa42dd70cbdc082b916da7fb925c542173e36bce97ea3e804923fe92c0ad991434e4a38327e15a1b5b5f945d66df615ae6d
-  languageName: node
-  linkType: hard
-
 "msgpackr-extract@npm:^2.2.0":
   version: 2.2.0
   resolution: "msgpackr-extract@npm:2.2.0"
@@ -12002,22 +11627,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"multer@npm:1.4.4":
-  version: 1.4.4
-  resolution: "multer@npm:1.4.4"
-  dependencies:
-    append-field: ^1.0.0
-    busboy: ^0.2.11
-    concat-stream: ^1.5.2
-    mkdirp: ^0.5.4
-    object-assign: ^4.1.1
-    on-finished: ^2.3.0
-    type-is: ^1.6.4
-    xtend: ^4.0.0
-  checksum: b5550d250aeee9c4d630eaecd133af0899239f6b10cec4b448ddd0a808025b383520b8227198a8612f60c2cd2094bcb60de93d973084f889d4e40efe6dbd641e
-  languageName: node
-  linkType: hard
-
 "multi-integer-range@npm:3.0.0":
   version: 3.0.0
   resolution: "multi-integer-range@npm:3.0.0"
@@ -12039,7 +11648,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"mz@npm:^2.4.0, mz@npm:^2.7.0":
+"mz@npm:^2.4.0":
   version: 2.7.0
   resolution: "mz@npm:2.7.0"
   dependencies:
@@ -12632,6 +12241,13 @@ __metadata:
   languageName: node
   linkType: hard
 
+"obliterator@npm:^2.0.1":
+  version: 2.0.4
+  resolution: "obliterator@npm:2.0.4"
+  checksum: f28ad35b6d812089315f375dc3e6e5f9bebf958ebe4b10ccd471c7115cbcf595e74bdac4783ae758e5b1f47e3096427fdb37cfa7bed566b132df92ff317b9a7c
+  languageName: node
+  linkType: hard
+
 "oblivious-set@npm:1.1.1":
   version: 1.1.1
   resolution: "oblivious-set@npm:1.1.1"
@@ -12646,7 +12262,14 @@ __metadata:
   languageName: node
   linkType: hard
 
-"on-finished@npm:^2.3.0":
+"on-exit-leak-free@npm:^2.1.0":
+  version: 2.1.0
+  resolution: "on-exit-leak-free@npm:2.1.0"
+  checksum: 7334d98b87b0c89c9b69c747760b21196ff35afdedc4eaf1a0a3a02964463d7f6802481b120e4c8298967c74773ca7b914ab2eb3d9b279010eb7f67ac4960eed
+  languageName: node
+  linkType: hard
+
+"on-finished@npm:2.4.1, on-finished@npm:^2.3.0":
   version: 2.4.1
   resolution: "on-finished@npm:2.4.1"
   dependencies:
@@ -12981,13 +12604,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"passthrough-counter@npm:^1.0.0":
-  version: 1.0.0
-  resolution: "passthrough-counter@npm:1.0.0"
-  checksum: 942a0addeb677e24ddb154b04cc29ce1c5720032efc268689446420f9350d47e94f2f1f76d469686bc87c1543c2f2165f2d004d265fe1b81465c76e02d272c63
-  languageName: node
-  linkType: hard
-
 "path-dirname@npm:^1.0.0":
   version: 1.0.2
   resolution: "path-dirname@npm:1.0.2"
@@ -13011,7 +12627,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"path-is-absolute@npm:1.0.1, path-is-absolute@npm:^1.0.0":
+"path-is-absolute@npm:^1.0.0":
   version: 1.0.1
   resolution: "path-is-absolute@npm:1.0.1"
   checksum: 060840f92cf8effa293bcc1bea81281bd7d363731d214cbe5c227df207c34cd727430f70c6037b5159c8a870b9157cba65e775446b0ab06fd5ecc7e54615a3b8
@@ -13062,13 +12678,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"path-to-regexp@npm:^6.1.0":
-  version: 6.2.1
-  resolution: "path-to-regexp@npm:6.2.1"
-  checksum: f0227af8284ea13300f4293ba111e3635142f976d4197f14d5ad1f124aebd9118783dd2e5f1fe16f7273743cc3dbeddfb7493f237bb27c10fdae07020cc9b698
-  languageName: node
-  linkType: hard
-
 "path-type@npm:^1.0.0":
   version: 1.1.0
   resolution: "path-type@npm:1.1.0"
@@ -13217,13 +12826,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"pify@npm:^4.0.1":
-  version: 4.0.1
-  resolution: "pify@npm:4.0.1"
-  checksum: 9c4e34278cb09987685fa5ef81499c82546c033713518f6441778fbec623fc708777fe8ac633097c72d88470d5963094076c7305cafc7ad340aae27cfacd856b
-  languageName: node
-  linkType: hard
-
 "pinkie-promise@npm:^2.0.0":
   version: 2.0.1
   resolution: "pinkie-promise@npm:2.0.1"
@@ -13240,6 +12842,44 @@ __metadata:
   languageName: node
   linkType: hard
 
+"pino-abstract-transport@npm:v1.0.0":
+  version: 1.0.0
+  resolution: "pino-abstract-transport@npm:1.0.0"
+  dependencies:
+    readable-stream: ^4.0.0
+    split2: ^4.0.0
+  checksum: 05dd0eda52dd99fd204b39fe7b62656744b63e863bc052cdd5105d25f226a236966d0a46e39a1ace4838f6e988c608837ff946d2d0bc92835ca7baa0a3bff8d8
+  languageName: node
+  linkType: hard
+
+"pino-std-serializers@npm:^6.0.0":
+  version: 6.0.0
+  resolution: "pino-std-serializers@npm:6.0.0"
+  checksum: d9dc1779b3870cdbe00dc2dff15e3931eb126bb144bc9f746d83a2c1174a28e366ed0abe63379dee2fee474e6018a088bfbb2c4b57c1e206601918f5a61e276f
+  languageName: node
+  linkType: hard
+
+"pino@npm:^8.5.0":
+  version: 8.7.0
+  resolution: "pino@npm:8.7.0"
+  dependencies:
+    atomic-sleep: ^1.0.0
+    fast-redact: ^3.1.1
+    on-exit-leak-free: ^2.1.0
+    pino-abstract-transport: v1.0.0
+    pino-std-serializers: ^6.0.0
+    process-warning: ^2.0.0
+    quick-format-unescaped: ^4.0.3
+    real-require: ^0.2.0
+    safe-stable-stringify: ^2.3.1
+    sonic-boom: ^3.1.0
+    thread-stream: ^2.0.0
+  bin:
+    pino: bin.js
+  checksum: 4aa2e320aa88f4a90fd25884ee4e3b9ef7963b3c59c514f3693b5a5c987b112cf3ab4e39a8c51efe32c861f5c058d7cfa7fcda59d964ed878f842fdbc6ab2876
+  languageName: node
+  linkType: hard
+
 "pirates@npm:^4.0.4":
   version: 4.0.5
   resolution: "pirates@npm:4.0.5"
@@ -13728,17 +13368,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"pretty@npm:^2.0.0":
-  version: 2.0.0
-  resolution: "pretty@npm:2.0.0"
-  dependencies:
-    condense-newlines: ^0.2.1
-    extend-shallow: ^2.0.1
-    js-beautify: ^1.6.12
-  checksum: 9c41ae0559195af2fb2496d84c6f442843e045d269d4008a6dd336f8372d7481395ed5ab23e5711b6172682c27cb0542e1ab3ca11b38da48f1109c0b701d0ef9
-  languageName: node
-  linkType: hard
-
 "prismjs@npm:1.29.0":
   version: 1.29.0
   resolution: "prismjs@npm:1.29.0"
@@ -13788,6 +13417,20 @@ __metadata:
   languageName: node
   linkType: hard
 
+"process-warning@npm:^2.0.0":
+  version: 2.0.0
+  resolution: "process-warning@npm:2.0.0"
+  checksum: a2bb299835bced58e63cbe06a8fd6e048a648d3649e81b62c442b63112a3f0a86912e7b1a9c557daca30652232d3b0a7f1972fb87c36334e2a5a6f3d5c4a76c9
+  languageName: node
+  linkType: hard
+
+"process@npm:^0.11.10":
+  version: 0.11.10
+  resolution: "process@npm:0.11.10"
+  checksum: bfcce49814f7d172a6e6a14d5fa3ac92cc3d0c3b9feb1279774708a719e19acd673995226351a082a9ae99978254e320ccda4240ddc474ba31a76c79491ca7c3
+  languageName: node
+  linkType: hard
+
 "progress@npm:^2.0.0":
   version: 2.0.3
   resolution: "progress@npm:2.0.3"
@@ -13838,10 +13481,13 @@ __metadata:
   languageName: node
   linkType: hard
 
-"proto-list@npm:~1.2.1":
-  version: 1.2.4
-  resolution: "proto-list@npm:1.2.4"
-  checksum: 4d4826e1713cbfa0f15124ab0ae494c91b597a3c458670c9714c36e8baddf5a6aad22842776f2f5b137f259c8533e741771445eb8df82e861eea37a6eaba03f7
+"proxy-addr@npm:^2.0.7":
+  version: 2.0.7
+  resolution: "proxy-addr@npm:2.0.7"
+  dependencies:
+    forwarded: 0.2.0
+    ipaddr.js: 1.9.1
+  checksum: 29c6990ce9364648255454842f06f8c46fcd124d3e6d7c5066df44662de63cdc0bad032e9bf5a3d653ff72141cc7b6019873d685708ac8210c30458ad99f2b74
   languageName: node
   linkType: hard
 
@@ -13863,13 +13509,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"pseudomap@npm:^1.0.2":
-  version: 1.0.2
-  resolution: "pseudomap@npm:1.0.2"
-  checksum: 856c0aae0ff2ad60881168334448e898ad7a0e45fe7386d114b150084254c01e200c957cf378378025df4e052c7890c5bd933939b0e0d2ecfcc1dc2f0b2991f5
-  languageName: node
-  linkType: hard
-
 "psl@npm:^1.1.28, psl@npm:^1.1.33":
   version: 1.9.0
   resolution: "psl@npm:1.9.0"
@@ -13988,7 +13627,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"pug@npm:*, pug@npm:3.0.2":
+"pug@npm:3.0.2":
   version: 3.0.2
   resolution: "pug@npm:3.0.2"
   dependencies:
@@ -14060,13 +13699,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"q@npm:1.4.1":
-  version: 1.4.1
-  resolution: "q@npm:1.4.1"
-  checksum: 22c8e1f24f416d0977e6da63f24712189c5dd789489999fc040467480e4e0ef4bd0e3126cce1b8ef72c709bbe1fcce10eba0f4991a03fc64ecb5a17e05ed8d35
-  languageName: node
-  linkType: hard
-
 "q@npm:^1.1.2":
   version: 1.5.1
   resolution: "q@npm:1.5.1"
@@ -14088,15 +13720,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"qs@npm:^6.4.0, qs@npm:^6.5.2":
-  version: 6.11.0
-  resolution: "qs@npm:6.11.0"
-  dependencies:
-    side-channel: ^1.0.4
-  checksum: 6e1f29dd5385f7488ec74ac7b6c92f4d09a90408882d0c208414a34dd33badc1a621019d4c799a3df15ab9b1d0292f97c1dd71dc7c045e69f81a8064e5af7297
-  languageName: node
-  linkType: hard
-
 "qs@npm:~6.5.2":
   version: 6.5.3
   resolution: "qs@npm:6.5.3"
@@ -14149,6 +13772,13 @@ __metadata:
   languageName: node
   linkType: hard
 
+"quick-format-unescaped@npm:^4.0.3":
+  version: 4.0.4
+  resolution: "quick-format-unescaped@npm:4.0.4"
+  checksum: 7bc32b99354a1aa46c089d2a82b63489961002bb1d654cee3e6d2d8778197b68c2d854fd23d8422436ee1fdfd0abaddc4d4da120afe700ade68bd357815b26fd
+  languageName: node
+  linkType: hard
+
 "quick-lru@npm:^5.1.1":
   version: 5.1.1
   resolution: "quick-lru@npm:5.1.1"
@@ -14165,6 +13795,13 @@ __metadata:
   languageName: node
   linkType: hard
 
+"range-parser@npm:~1.2.1":
+  version: 1.2.1
+  resolution: "range-parser@npm:1.2.1"
+  checksum: 0a268d4fea508661cf5743dfe3d5f47ce214fd6b7dec1de0da4d669dd4ef3d2144468ebe4179049eff253d9d27e719c88dae55be64f954e80135a0cada804ec9
+  languageName: node
+  linkType: hard
+
 "rangestr@npm:0.0.1":
   version: 0.0.1
   resolution: "rangestr@npm:0.0.1"
@@ -14179,18 +13816,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"raw-body@npm:^2.2.0, raw-body@npm:^2.3.3":
-  version: 2.5.1
-  resolution: "raw-body@npm:2.5.1"
-  dependencies:
-    bytes: 3.1.2
-    http-errors: 2.0.0
-    iconv-lite: 0.4.24
-    unpipe: 1.0.0
-  checksum: 5362adff1575d691bb3f75998803a0ffed8c64eabeaa06e54b4ada25a0cd1b2ae7f4f5ec46565d1bec337e08b5ac90c76eaa0758de6f72a633f025d754dec29e
-  languageName: node
-  linkType: hard
-
 "rc@npm:^1.2.7":
   version: 1.2.8
   resolution: "rc@npm:1.2.8"
@@ -14253,18 +13878,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"readable-stream@npm:1.1.x, readable-stream@npm:~1.1.9":
-  version: 1.1.14
-  resolution: "readable-stream@npm:1.1.14"
-  dependencies:
-    core-util-is: ~1.0.0
-    inherits: ~2.0.1
-    isarray: 0.0.1
-    string_decoder: ~0.10.x
-  checksum: 17dfeae3e909945a4a1abc5613ea92d03269ef54c49288599507fc98ff4615988a1c39a999dcf9aacba70233d9b7040bc11a5f2bfc947e262dedcc0a8b32b5a0
-  languageName: node
-  linkType: hard
-
 "readable-stream@npm:3, readable-stream@npm:^3.1.1, readable-stream@npm:^3.4.0, readable-stream@npm:^3.6.0":
   version: 3.6.0
   resolution: "readable-stream@npm:3.6.0"
@@ -14291,6 +13904,30 @@ __metadata:
   languageName: node
   linkType: hard
 
+"readable-stream@npm:^4.0.0":
+  version: 4.2.0
+  resolution: "readable-stream@npm:4.2.0"
+  dependencies:
+    abort-controller: ^3.0.0
+    buffer: ^6.0.3
+    events: ^3.3.0
+    process: ^0.11.10
+  checksum: aa8447f781e6df90af78f6b0b9b9a77da2816dcf6c8220e7021c4de36e04f8129fed7ead81eac0baad2f42098209f9e7d7cd43169e1c156efcd2613828a37439
+  languageName: node
+  linkType: hard
+
+"readable-stream@npm:~1.1.9":
+  version: 1.1.14
+  resolution: "readable-stream@npm:1.1.14"
+  dependencies:
+    core-util-is: ~1.0.0
+    inherits: ~2.0.1
+    isarray: 0.0.1
+    string_decoder: ~0.10.x
+  checksum: 17dfeae3e909945a4a1abc5613ea92d03269ef54c49288599507fc98ff4615988a1c39a999dcf9aacba70233d9b7040bc11a5f2bfc947e262dedcc0a8b32b5a0
+  languageName: node
+  linkType: hard
+
 "readable-web-to-node-stream@npm:^3.0.2":
   version: 3.0.2
   resolution: "readable-web-to-node-stream@npm:3.0.2"
@@ -14318,6 +13955,13 @@ __metadata:
   languageName: node
   linkType: hard
 
+"real-require@npm:^0.2.0":
+  version: 0.2.0
+  resolution: "real-require@npm:0.2.0"
+  checksum: fa060f19f2f447adf678d1376928c76379dce5f72bd334da301685ca6cdcb7b11356813332cc243c88470796bc2e2b1e2917fc10df9143dd93c2ea608694971d
+  languageName: node
+  linkType: hard
+
 "rechoir@npm:^0.6.2":
   version: 0.6.2
   resolution: "rechoir@npm:0.6.2"
@@ -14348,15 +13992,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"redis-info@npm:^3.0.8":
-  version: 3.1.0
-  resolution: "redis-info@npm:3.1.0"
-  dependencies:
-    lodash: ^4.17.11
-  checksum: d72ff0584ebb4a2149cfcfcf9142d9a7f9d0b96ae53fbf431f2738f33f1f42add6505ff73b2d640cab345923a34b217d7c728fa706cc81ad8bd8ad4c48987445
-  languageName: node
-  linkType: hard
-
 "redis-lock@npm:0.1.4":
   version: 0.1.4
   resolution: "redis-lock@npm:0.1.4"
@@ -14657,16 +14292,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"resolve-path@npm:^1.4.0":
-  version: 1.4.0
-  resolution: "resolve-path@npm:1.4.0"
-  dependencies:
-    http-errors: ~1.6.2
-    path-is-absolute: 1.0.1
-  checksum: 1a39f569ee54dd5f8ee8576ef8671c9724bea65d9f9982fbb5352af9fb4e500e1e459c1bfb1ae3ebfd8d43a709c3a01dfa4f46cf5b831e45e2caed4f1a208300
-  languageName: node
-  linkType: hard
-
 "resolve-url@npm:^0.2.1":
   version: 0.2.1
   resolution: "resolve-url@npm:0.2.1"
@@ -14742,6 +14367,13 @@ __metadata:
   languageName: node
   linkType: hard
 
+"ret@npm:~0.2.0":
+  version: 0.2.2
+  resolution: "ret@npm:0.2.2"
+  checksum: 774964bb413a3525e687bca92d81c1cd75555ec33147c32ecca22f3d06409e35df87952cfe3d57afff7650a0f7e42139cf60cb44e94c29dde390243bc1941f16
+  languageName: node
+  linkType: hard
+
 "retry@npm:^0.12.0":
   version: 0.12.0
   resolution: "retry@npm:0.12.0"
@@ -14756,7 +14388,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"rfdc@npm:^1.3.0":
+"rfdc@npm:^1.2.0, rfdc@npm:^1.3.0":
   version: 1.3.0
   resolution: "rfdc@npm:1.3.0"
   checksum: fb2ba8512e43519983b4c61bd3fa77c0f410eff6bae68b08614437bc3f35f91362215f7b4a73cbda6f67330b5746ce07db5dd9850ad3edc91271ad6deea0df32
@@ -14890,6 +14522,15 @@ __metadata:
   languageName: node
   linkType: hard
 
+"safe-regex2@npm:^2.0.0":
+  version: 2.0.0
+  resolution: "safe-regex2@npm:2.0.0"
+  dependencies:
+    ret: ~0.2.0
+  checksum: f5e182fca040dedd50ae052ea0eb035d9903b2db71243d5d8b43299735857288ef2ab52546a368d9c6fd1333b2a0d039297925e78ffc14845354f3f6158af7c2
+  languageName: node
+  linkType: hard
+
 "safe-regex@npm:^1.1.0":
   version: 1.1.0
   resolution: "safe-regex@npm:1.1.0"
@@ -14899,6 +14540,13 @@ __metadata:
   languageName: node
   linkType: hard
 
+"safe-stable-stringify@npm:^2.3.1":
+  version: 2.4.1
+  resolution: "safe-stable-stringify@npm:2.4.1"
+  checksum: d8e505c462031301040605a4836ca25b52a1744eff01b0939b4d43136638fb8e88e0cec3d3ab6ab8e26f501086e6ba6bf34b228f57bf2ac56cb8d4061355d723
+  languageName: node
+  linkType: hard
+
 "safer-buffer@npm:>= 2.1.2 < 3, safer-buffer@npm:>= 2.1.2 < 3.0.0, safer-buffer@npm:^2.0.2, safer-buffer@npm:^2.1.0, safer-buffer@npm:~2.1.0":
   version: 2.1.2
   resolution: "safer-buffer@npm:2.1.2"
@@ -14967,7 +14615,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"secure-json-parse@npm:^2.4.0":
+"secure-json-parse@npm:^2.4.0, secure-json-parse@npm:^2.5.0":
   version: 2.5.0
   resolution: "secure-json-parse@npm:2.5.0"
   checksum: 84147a32615ce0d93d2fbba60cde85ae362f45cc948ea134e4d6d1e678bb4b7f3a5ce9b9692ed052baefeb2e1c8ba183b34920390e6a089925b97b0d8f7ab064
@@ -14997,7 +14645,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"semver@npm:2 || 3 || 4 || 5, semver@npm:^5.6.0":
+"semver@npm:2 || 3 || 4 || 5":
   version: 5.7.1
   resolution: "semver@npm:5.7.1"
   bin:
@@ -15026,6 +14674,27 @@ __metadata:
   languageName: node
   linkType: hard
 
+"send@npm:^0.18.0":
+  version: 0.18.0
+  resolution: "send@npm:0.18.0"
+  dependencies:
+    debug: 2.6.9
+    depd: 2.0.0
+    destroy: 1.2.0
+    encodeurl: ~1.0.2
+    escape-html: ~1.0.3
+    etag: ~1.8.1
+    fresh: 0.5.2
+    http-errors: 2.0.0
+    mime: 1.6.0
+    ms: 2.1.3
+    on-finished: 2.4.1
+    range-parser: ~1.2.1
+    statuses: 2.0.1
+  checksum: 74fc07ebb58566b87b078ec63e5a3e41ecd987e4272ba67b7467e86c6ad51bc6b0b0154133b6d8b08a2ddda360464f71382f7ef864700f34844a76c8027817a8
+  languageName: node
+  linkType: hard
+
 "set-blocking@npm:^2.0.0":
   version: 2.0.0
   resolution: "set-blocking@npm:2.0.0"
@@ -15033,6 +14702,13 @@ __metadata:
   languageName: node
   linkType: hard
 
+"set-cookie-parser@npm:^2.4.1":
+  version: 2.5.1
+  resolution: "set-cookie-parser@npm:2.5.1"
+  checksum: b99c37f976e68ae6eb7c758bf2bbce1e60bb54e3eccedaa25f2da45b77b9cab58d90674cf9edd7aead6fbeac6308f2eb48713320a47ca120d0e838d0194513b6
+  languageName: node
+  linkType: hard
+
 "set-value@npm:^2.0.0, set-value@npm:^2.0.1":
   version: 2.0.1
   resolution: "set-value@npm:2.0.1"
@@ -15052,13 +14728,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"setprototypeof@npm:1.1.0":
-  version: 1.1.0
-  resolution: "setprototypeof@npm:1.1.0"
-  checksum: 27cb44304d6c9e1a23bc6c706af4acaae1a7aa1054d4ec13c05f01a99fd4887109a83a8042b67ad90dbfcd100d43efc171ee036eb080667172079213242ca36e
-  languageName: node
-  linkType: hard
-
 "setprototypeof@npm:1.2.0":
   version: 1.2.0
   resolution: "setprototypeof@npm:1.2.0"
@@ -15122,13 +14791,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"sigmund@npm:^1.0.1":
-  version: 1.0.1
-  resolution: "sigmund@npm:1.0.1"
-  checksum: 793f81f8083ad75ff3903ffd93cf35be8d797e872822cf880aea27ce6db522b508d93ea52ae292bccf357ce34dd5c7faa544cc51c2216e70bbf5fcf09b62707c
-  languageName: node
-  linkType: hard
-
 "signal-exit@npm:^3.0.0, signal-exit@npm:^3.0.2, signal-exit@npm:^3.0.3, signal-exit@npm:^3.0.7":
   version: 3.0.7
   resolution: "signal-exit@npm:3.0.7"
@@ -15270,6 +14932,15 @@ __metadata:
   languageName: node
   linkType: hard
 
+"sonic-boom@npm:^3.1.0":
+  version: 3.2.0
+  resolution: "sonic-boom@npm:3.2.0"
+  dependencies:
+    atomic-sleep: ^1.0.0
+  checksum: 526669b78e0ac3bcbe2a53e5ac8960d3b25e61d8e6a46eaed5a0c46d7212c5f638bb136236870babedfcb626063711ba8f81e538f88b79e6a90a5b2ff71943b4
+  languageName: node
+  linkType: hard
+
 "sort-keys@npm:^1.0.0":
   version: 1.1.2
   resolution: "sort-keys@npm:1.1.2"
@@ -15413,7 +15084,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"split2@npm:^4.1.0":
+"split2@npm:^4.0.0, split2@npm:^4.1.0":
   version: 4.1.0
   resolution: "split2@npm:4.1.0"
   checksum: ec581597cb74c13cdfb5e2047543dd40cb1e8e9803c7b1e0c29ede05f2b4f049b2d6e7f2788a225d544549375719658b8f38e9366364dec35dc7a12edfda5ee5
@@ -15532,7 +15203,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"statuses@npm:>= 1.4.0 < 2, statuses@npm:>= 1.5.0 < 2, statuses@npm:^1.5.0":
+"statuses@npm:>= 1.5.0 < 2, statuses@npm:^1.5.0":
   version: 1.5.0
   resolution: "statuses@npm:1.5.0"
   checksum: c469b9519de16a4bb19600205cffb39ee471a5f17b82589757ca7bd40a8d92ebb6ed9f98b5a540c5d302ccbc78f15dc03cc0280dd6e00df1335568a5d5758a5c
@@ -15571,10 +15242,10 @@ __metadata:
   languageName: node
   linkType: hard
 
-"streamsearch@npm:0.1.2":
-  version: 0.1.2
-  resolution: "streamsearch@npm:0.1.2"
-  checksum: d2db57cbfbf7947ab9c75a7b4c80a8ef8d24850cf0a1a24258bb6956c97317ce1eab7dbcbf9c5aba3e6198611af1053b02411057bbedb99bf9c64b8275248997
+"stream-wormhole@npm:^1.1.0":
+  version: 1.1.0
+  resolution: "stream-wormhole@npm:1.1.0"
+  checksum: cc19e0235c5d031bd530fa83913c807d9525fa4ba33d51691dd822c0726b8b7ef138b34f289d063a3018cddba67d3ba7fd0ecedaa97242a0f1ed2eed3c6a2ab1
   languageName: node
   linkType: hard
 
@@ -15999,6 +15670,13 @@ __metadata:
   languageName: node
   linkType: hard
 
+"text-decoding@npm:^1.0.0":
+  version: 1.0.0
+  resolution: "text-decoding@npm:1.0.0"
+  checksum: 4b2359d8efdabea72ac470304e991913e9b82a55b1c33ab5204f115d11305ac5900add80aee5f7d22b2bcf0faebaf35b193d28a10b74adf175d9ac9d63604445
+  languageName: node
+  linkType: hard
+
 "text-table@npm:^0.2.0":
   version: 0.2.0
   resolution: "text-table@npm:0.2.0"
@@ -16038,6 +15716,15 @@ __metadata:
   languageName: node
   linkType: hard
 
+"thread-stream@npm:^2.0.0":
+  version: 2.2.0
+  resolution: "thread-stream@npm:2.2.0"
+  dependencies:
+    real-require: ^0.2.0
+  checksum: b7f0ee166ed17ac54700a0b6fc291967c97785b458ff54efe5431a7281bb52d1163e6ec550a614f2a47f0f02de5b35a342bd5acd215af23030938c64859152b2
+  languageName: node
+  linkType: hard
+
 "three@npm:0.146.0":
   version: 0.146.0
   resolution: "three@npm:0.146.0"
@@ -16109,6 +15796,13 @@ __metadata:
   languageName: node
   linkType: hard
 
+"tiny-lru@npm:^10.0.0":
+  version: 10.0.1
+  resolution: "tiny-lru@npm:10.0.1"
+  checksum: 58b5f17a357625335aa3b90ee8c9b3e9abede5c1f46066c73deb129574a205efb112807d6d473909e73f1d874ea99bf14eb5c88223d540eb32ebb5e1ff146689
+  languageName: node
+  linkType: hard
+
 "tinycolor2@npm:1.4.2":
   version: 1.4.2
   resolution: "tinycolor2@npm:1.4.2"
@@ -16460,7 +16154,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"type-is@npm:^1.6.14, type-is@npm:^1.6.16, type-is@npm:^1.6.4":
+"type-is@npm:^1.6.16":
   version: 1.6.18
   resolution: "type-is@npm:1.6.18"
   dependencies:
@@ -16749,13 +16443,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"unpipe@npm:1.0.0":
-  version: 1.0.0
-  resolution: "unpipe@npm:1.0.0"
-  checksum: 4fa18d8d8d977c55cb09715385c203197105e10a6d220087ec819f50cb68870f02942244f1017565484237f1f8c5d3cd413631b1ae104d3096f24fdfde1b4aa2
-  languageName: node
-  linkType: hard
-
 "unset-value@npm:^1.0.0":
   version: 1.0.0
   resolution: "unset-value@npm:1.0.0"
@@ -16981,7 +16668,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"vary@npm:^1.1.2":
+"vary@npm:1.1.2, vary@npm:^1.1.2":
   version: 1.1.2
   resolution: "vary@npm:1.1.2"
   checksum: ae0123222c6df65b437669d63dfa8c36cee20a504101b2fcd97b8bf76f91259c17f9f2b4d70a1e3c6bbcee7f51b28392833adb6b2770b23b01abec84e369660b
@@ -17554,13 +17241,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"yallist@npm:^2.1.2":
-  version: 2.1.2
-  resolution: "yallist@npm:2.1.2"
-  checksum: 9ba99409209f485b6fcb970330908a6d41fa1c933f75e08250316cce19383179a6b70a7e0721b89672ebb6199cc377bf3e432f55100da6a7d6e11902b0a642cb
-  languageName: node
-  linkType: hard
-
 "yallist@npm:^3.0.0, yallist@npm:^3.1.1":
   version: 3.1.1
   resolution: "yallist@npm:3.1.1"