Guides
Middleware
認証などのリクエスト処理を、ページや Server Function で共通化するためのガイド。
Effront の Middleware は、ページのリクエストや Server Function の呼び出しに対して、事前のチェックやリクエスト固有のサービスの提供を行います。 認証の確認や、アプリケーションから現在のユーザーを参照できるようにする処理などを共通化できます。
適用するリクエストを選ぶ
Middleware を適用できる対象:
- ルート
- Server Function
共通の Middleware 適用済みアプリケーション定義から Routes と Server Function を作成すると、同じ Middleware をそれぞれに適用できます。
独自の HTTP エンドポイントや、ルートに一致しないリクエストにも適用する場合は、グローバル HTTP Middleware を使います。 ホストが直接配信する静的アセットには、ホスト側の設定が必要です。
リクエストのサービスを提供する
src/request-scope.ts を作り、現在のリクエスト URL をサービスとして提供します。
import { Context, Effect } from "effect";
import { HttpServerRequest } from "effect/unstable/http";
import { Application } from "@effront/core";
export class RequestInfo extends Context.Service<RequestInfo, { readonly url: string }>()(
"app/middleware/RequestInfo",
) {}
export const EFFRONT = Application.effront();
const WithRequestInfo = EFFRONT.Middleware.make<{ provides: RequestInfo }>(
Effect.fn(function* (httpEffect) {
const request = yield* HttpServerRequest.HttpServerRequest;
return yield* httpEffect.pipe(Effect.provideService(RequestInfo, { url: request.url }));
}),
);
export const RequestEFFRONT = EFFRONT.withMiddleware(WithRequestInfo);provides: RequestInfo でサービスを宣言し、Effect.provideService で後続の処理に値を提供します。
リクエストを続行するには httpEffect を実行します。
このスコープの利用側と Routes は、派生した RequestEFFRONT から定義します。
Page にサービスを適用する
src/entry.effront.tsx で共有の定義を使い、ホームページを /request に置き換えます。
// src/entry.effront.tsx: replace the Application import.
import { EFFRONT, RequestEFFRONT, RequestInfo } from "./request-scope";
// Remove the local EFFRONT declaration.
// Replace HomePage with RequestPage.
const RequestPage = RequestEFFRONT.Page.make({
render: Effect.fn("RequestPage.render")(function* () {
const info = yield* RequestInfo;
return <p>Request URL: {info.url}</p>;
}),
});
// Replace the default export with this route declaration and export.
const routes = RequestEFFRONT.Routes.make({ layout: RootLayout }).page("/request", RequestPage);
export default EFFRONT.make({ routes });ブラウザーで /request を開くと、リクエスト URL が表示されます。
RequestEFFRONT から Page を作るだけでは不十分で、Routes でも Middleware を有効にする必要があります。
RequestEFFRONT から作った Layout と Component も、このスコープ内で描画される場合にサービスを読み取れます。
一つのセクションに限定するには、この Routes を親 Routes に mount します。
後続の処理を止めて応答する
後続の処理を止めるには、httpEffect を実行せずにレスポンスを返します。
例えば、src/maintenance.ts を作成します。
import { Effect } from "effect";
import { HttpServerResponse } from "effect/unstable/http";
import { EFFRONT } from "./request-scope";
export const Maintenance = EFFRONT.Middleware.make(() =>
Effect.succeed(HttpServerResponse.text("Under maintenance", { status: 503 })),
);src/entry.effront.tsx で Maintenance を import し、Routes に適用します。
// src/entry.effront.tsx: add to the imports.
import { Maintenance } from "./maintenance";
// Replace routes to apply Maintenance.
const routes = RequestEFFRONT.withMiddleware(Maintenance)
.Routes.make({
layout: RootLayout,
})
.page("/request", RequestPage);Page の代わりに、ステータス 503 と Under maintenance が返ります。
条件付きのチェックでは、リクエストを許可する場合にだけ httpEffect を実行してください。
認証も同じ流れです。 セッションを検証し、不正なリクエストを拒否し、続行前に検証済みの利用者を提供します。
Middleware は宣言順に入り、レスポンスは逆順に処理します。 途中で応答すると、残りの内側のハンドラーは実行されません。 一つのチェーンに同じ Middleware を二度追加しないでください。
Server Function にチェックを適用する
src/record-request.ts で、Middleware 付きの RequestEFFRONT からアクションを定義します。
"use server";
import { Effect, Schema } from "effect";
import { RequestEFFRONT, RequestInfo } from "./request-scope";
export const recordRequest = RequestEFFRONT.ServerFn.make({
input: Schema.fromFormData(Schema.Struct({})),
handler: Effect.fn("recordRequest")(function* () {
const info = yield* RequestInfo;
yield* Effect.logInfo("Form received", { url: info.url });
}),
});src/entry.effront.tsx で Maintenance を削除して Routes を元に戻し、RequestPage にフォームを追加します。
// src/entry.effront.tsx: replace the Maintenance import.
import { recordRequest } from "./record-request";
// Replace RequestPage to include the form.
const RequestPage = RequestEFFRONT.Page.make({
render: Effect.fn("RequestPage.render")(function* () {
const info = yield* RequestInfo;
return (
<>
<p>Request URL: {info.url}</p>
<form action={recordRequest}>
<button type="submit">Record request</button>
</form>
</>
);
}),
});
// Replace routes to remove Maintenance.
const routes = RequestEFFRONT.Routes.make({ layout: RootLayout }).page("/request", RequestPage);送信すると、Form received と送信時の URL がログに出ます。
ページを開いたときの値を保存して使うわけではありません。
フォームの状態を返す方法は Mutation Server Function を参照してください。