server$()

server$() は、サーバー上でのみ実行される関数を定義できるため、サーバー専用の操作やデータベースアクセスに最適です。クライアントとサーバー間のRPC(リモートプロシージャコール)メカニズムとして機能します。これは従来のHTTPエンドポイントに似ていますが、TypeScriptで強く型付けされており、保守が容易です。

server$ は、任意の数の引数を受け取り、Qwikによってシリアライズできる任意の値を返すことができます。プリミティブ、オブジェクト、配列、ビッグイント、JSXノード、さらにはPromiseなど、いくつか例を挙げると、これらが含まれます。

`AbortSignal` はオプションで、接続を終了することで長時間実行されるリクエストをキャンセルできます。
新しい関数は次のシグネチャを持ちます。
([AbortSignal, ...yourOtherArgs]): Promise<T>

サーバーランタイムによっては、サーバー上の関数がすぐに終了しない場合があることに注意してください。これは、ランタイムによってクライアントの切断がどのように処理されるかによって異なります。

import { component$, useSignal } from '@builder.io/qwik';
import { server$ } from '@builder.io/qwik-city';
 
// By wrapping a function with `server$()` we mark it to always
// execute on the server. This is a form of RPC mechanism.
export const serverGreeter = server$(
  function (firstName: string, lastName: string) {
    const greeting = `Hello ${firstName} ${lastName}`;
    console.log('Prints in the server', greeting);
    return greeting;
  }
);
 
export default component$(() => {
  const firstName = useSignal('');
  const lastName = useSignal('');
 
  return (
    <section>
      <label>First name: <input bind:value={firstName} /></label>
      <label>Last name: <input bind:value={lastName} /></label>
 
      <button
        onClick$={
          async () => {
            const greeting = await serverGreeter(firstName.value, lastName.value);
            alert(greeting);
          }
        }
      >
        greet
      </button>
    </section>
  );
});

`RequestEvent` を使用したリクエスト情報のアクセス

server$ を使用する場合、 `this` を介して `RequestEvent` オブジェクトにアクセスできます。このオブジェクトは、環境変数、Cookie、URL、ヘッダーなど、HTTPリクエストに関する役立つ情報を提供します。使用方法を以下に示します。

環境変数

`this.env.get()` を使用して環境変数にアクセスできます。

export const getEnvVariable = server$(
  function () {
    const dbKey = this.env.get('DB_KEY');
    console.log('Database Key:', dbKey);
    return dbKey;
  }
);

Cookie

`this.cookie.get()` と `this.cookie.set()` を使用してCookieを読み取ることができます。

`handleCookies` を(以下の例のように)初期リクエスト中に実行される `useTask$` 関数内で使用する場合、Cookieの設定は期待どおりに機能しません。これは、サーバーサイドレンダリング(SSR)中にレスポンスがストリーミングされ、HTTPでは最初のレスポンスを送信する前にすべてのヘッダーを設定する必要があるためです。ただし、 `handleCookies` が `useVisibleTask$` で使用されている場合、この問題は発生しません。初期ドキュメントリクエストにCookieを設定する必要がある場合は、 `plugin@<name>.ts` またはミドルウェアを使用できます。

export const handleCookies = server$(
  function () {
    const userSession = this.cookie.get('user-session')?.value;
    if (!userSession) {
      this.cookie.set('user-session', 'new-session-id', { path: '/', httpOnly: true });
    }
    return userSession;
  }
);

URL

`this.url` を使用して、リクエストURLとそのコンポーネントにアクセスできます。

export const getRequestUrl = server$(
  function () {
    const requestUrl = this.url;
    console.log('Request URL:', requestUrl);
    return requestUrl;
  }
);

ヘッダー

`this.headers.get()` を使用してヘッダーを読み取ることができます。

export const getHeaders = server$(
  function () {
    const userAgent = this.headers.get('User-Agent');
    console.log('User-Agent:', userAgent);
    return userAgent;
  }
);

複数の `RequestEvent` 情報の使用

環境変数、Cookie、URL、およびヘッダーを1つの関数で組み合わせた例を以下に示します。

export const handleRequest = server$(
  function () {
    // Access environment variable
    const dbKey = this.env.get('DB_KEY');
 
    // Access cookies
    const userSession = this.cookie.get('user-session')?.value;
    if (!userSession) {
      this.cookie.set('user-session', 'new-session-id', { path: '/', httpOnly: true });
    }
 
    // Access request URL
    const requestUrl = this.url;
 
    // Access headers
    const userAgent = this.headers.get('User-Agent');
 
    console.log('Environment Variable:', dbKey);
    console.log('User Session:', userSession);
    console.log('Request URL:', requestUrl);
    console.log('User-Agent:', userAgent);
 
    return {
      dbKey,
      userSession,
      requestUrl,
      userAgent
    };
  }
);

ストリーミングレスポンス

server$ は、asyncジェネレーター関数を使用してデータのストリームを返すことができます。これは、サーバーからクライアントにデータをストリーミングするのに役立ちます。

クライアント側でジェネレーターを終了する(たとえば、ジェネレーターで `.return()` を呼び出すか、async for-ofループから抜け出すことによって)と、接続が終了します。 `AbortSignal` と同様に、サーバー側でジェネレーターがどのように終了するかは、サーバーランタイムとクライアントの切断の処理方法によって異なります。

import { component$, useSignal } from '@builder.io/qwik';
import { server$ } from '@builder.io/qwik-city';
 
export const streamFromServer = server$(
  // Async Generator Function
  async function* () {
    // Creation of an array with 10 undefined values
    const iterationRange = Array(10).fill().entries(); 
  
    for (const [value] of iterationRange) {
      // Yield returns the array value during each iteration
      yield value;
  
      // Waiting for 1 second before the next iteration
      // This simulates a delay in the execution
      await new Promise((resolve) => setTimeout(resolve, 1000));
    }
  }
);
 
 
export default component$(() => {
  const message = useSignal('');
  return (
    <div>
      <button
        onClick$={
          async () => {
            // call the async stream function and wait for the response
            const response = await streamFromServer(); 
            // use a for-await-of loop to asynchronously iterate over the response
            for await (const value of response) {
              // add each value from the response to the message value
              message.value += ` ${value}`;
            }
            // do anything else
          }
        }
      >
        start
      </button>
      <div>{message.value}</div>
    </div>
  );
});

このAPIは、実際にドキュメントサイトでQwikGPTストリーミングレスポンスを実装するために使用されています。

server$() はどのように動作しますか?

server$() は関数をラップし、その関数のasyncプロキシを返します。サーバー上では、プロキシ関数はラップされた関数を直接呼び出し、 `server$()` 関数によってHTTPエンドポイントが自動的に作成されます。

クライアント上では、プロキシ関数は `fetch()` を使用して、HTTPリクエストを介してラップされた関数を呼び出します。

注意: server$() 関数は、サーバーとクライアントが同じバージョンのコードを実行していることを保証する必要があります。バージョンにずれがある場合、動作は未定義であり、エラーが発生する可能性があります。バージョンのずれが一般的な問題である場合は、tRPC やその他のライブラリなど、より正式な RPC メカニズムを使用する必要があります。

重要な注意点 onClick$ 内で server$() を定義および呼び出す場合、潜在的なエラーが発生する可能性があることに注意してください。それらを回避するには、ハンドラーに $ がラップされていることを確認してください。
これを行わないでください
onClick$={() => server$(() => // サーバーコード!)}
これを行ってください
onClick$={$(() => server$(() => // サーバーコード!))}

ミドルウェアと server$

server$ を使用する場合、ミドルウェア関数 がどのように実行されるかを理解することが重要です。 layout ファイルで定義されたミドルウェア関数は、server$ リクエストに対しては実行されません。これは、特に開発者がページリクエストと server$ リクエストの両方に対して特定のミドルウェアが実行されることを期待している場合に、混乱を招く可能性があります。

ミドルウェア関数が両方のタイプの要求に対して実行されるようにするには、plugin.ts ファイルで定義する必要があります。これにより、通常のページリクエストか server$ リクエストかに関係なく、すべての受信リクエストに対してミドルウェアが確実に実行されます。

plugin.ts ファイルでミドルウェアを定義することで、開発者は共有ミドルウェアロジックを一元管理し、一貫性を確保し、潜在的なエラーや見落としを減らすことができます。

貢献者

このドキュメントの改善に貢献してくれたすべての貢献者に感謝します!

  • mhevery
  • manucorporat
  • AnthonyPAlicea
  • the-r3aper7
  • igorbabko
  • RaeesBhatti
  • mrhoodz
  • DanielAdolfsson
  • mjschwanitz
  • wtlin1228
  • adamdbradley
  • jemsco
  • patrickjs