---
title: Call your API from React
description: Connect Vite or Next.js to your backend with generated types and display a validated response without importing server code.
pagination: false
sidebar:
  icon: panels-top-left
search:
  keywords:
    [frontend guide, React, typed client, defineRoute, Vite, Next.js, withRelkit, client pull]
  boost: 5
---

Show **Hello, RELKIT!** in a React page using the same `GET /hello` operation you ran in
[Create and run an API](/docs/learn/create-an-app). Your backend owns its input and output
schemas; the frontend receives generated types and calls it through RPC.

> **Before you begin**
>
> Keep that backend running on `http://127.0.0.1:3000`. You'll also need an existing Vite React or
> Next.js App Router TypeScript app with its dependencies installed. Run frontend commands in that
> app's root. This guide adds RELKIT to it; it does not require the fullstack or agent starter.

Select your framework in the tabs below. Your selection carries through the remaining steps.

1. ## 1. Install the frontend client

    Install the React client and its selected peers, preserving compatible versions already
    used by your app:

    {/* Generated by apps/docs/scripts/generate.ts. Do not edit. */}

    From this guide's application/package root, install only its selected requirements:

    ```sh
    bun add @opentelemetry/api@1.9.1 @orpc/tanstack-query@2.0.0-beta.31 @relkit/client@0.8.2 '@tanstack/query-core@>=5.80.2' '@tanstack/react-query@>=5.80.2' 'react@>=18.0.0'
    ```

    Keep these owner-specific versions and the lockfile together. Runtime SDKs belong in `dependencies`; build/type tooling belongs in `devDependencies`. Installing an adapter does not supply its optional SDK peers or enable the feature's configuration.

    Install the CLI as frontend development tooling so it can pull and check the contract:

    {/* Generated by apps/docs/scripts/generate.ts. Do not edit. */}

    From this guide's application/package root, install only its selected requirements:

    ```sh
    bun add --dev @relkit/cli@0.8.4
    ```

    Keep these owner-specific versions and the lockfile together. Runtime SDKs belong in `dependencies`; build/type tooling belongs in `devDependencies`. Installing an adapter does not supply its optional SDK peers or enable the feature's configuration.

    Keep your existing React DOM and TypeScript tooling. The frontend does not need the backend's
    database, storage, or model-provider packages.

2. ## 2. Pull the backend contract

    From the backend root, run `bun run check` after changing a route. With the backend running,
    run this in the **frontend root**:

    ```sh title="Terminal — frontend root"
    bun run relkit client pull http://127.0.0.1:3000 --out .relkit/generated
    ```

    This writes `contract.ts`, `client.ts`, `client-registry.d.ts`, and `client-manifest.json`.
    Keep them together and regenerate them with the command; do not edit them or import backend
    `service.ts` files into the browser.

    Include the generated declarations in the TypeScript project that checks your React source.

    **Vite**

    In a Vite app this is usually `tsconfig.app.json`. Merge these `include` entries into your existing
    configuration, keeping its compiler options:

    ```json title="tsconfig.app.json"
    {
      "compilerOptions": {
        "target": "ES2022",
        "lib": ["ES2022", "DOM", "DOM.Iterable"],
        "module": "ESNext",
        "moduleResolution": "Bundler",
        "jsx": "react-jsx",
        "strict": true,
        "skipLibCheck": true,
        "noEmit": true
      },
      "include": ["src", ".relkit/generated/**/*.ts"]
    }
    ```

    **Next.js**

    Merge these `include` entries into your existing `tsconfig.json`. Keep your compiler options,
    Next.js plugin, and any additional entries your application already uses:

    ```json title="tsconfig.json — merge the include list"
    {
      "include": [
        "next-env.d.ts",
        "**/*.ts",
        "**/*.tsx",
        ".next/types/**/*.ts",
        ".next/dev/types/**/*.ts",
        ".relkit/generated/**/*.ts"
      ]
    }
    ```

    Your editor should now offer `GET /hello` as a selector and an optional `name` string as its input.
    If the selector is missing, check the backend URL, the pull output, and the TypeScript `include` list.

3. ## 3. Connect the local frontend

    The build helper reads the fingerprint from the frontend's `.relkit/generated/client-manifest.json`.
    It does not pull a contract, generate types, or configure forwarding on its own.

    **Vite**

    Add `relkit()` to your existing Vite plugins and merge these proxy entries into `server.proxy`.
    Keep your framework's existing plugins:

    ```ts title="vite.config.ts — merge with your existing config"
    /** Adds the pulled fingerprint and a local same-origin proxy to an existing Vite React project. */
    import { relkit } from "@relkit/client/build/vite";

    export default {
      plugins: [relkit()],
      server: {
        host: "127.0.0.1",
        port: 5173,
        strictPort: true,
        proxy: {
          "/rpc": { target: "http://127.0.0.1:3000", ws: true },
          "/_relkit": { target: "http://127.0.0.1:3000" },
        },
      },
    };
    ```

    The browser sends `/rpc` calls and the `/_relkit/v1/client/identity` handshake to the frontend
    origin; Vite forwards them to the backend. This lets the provider use the current page's origin.
    Keep `127.0.0.1` consistent in these local URLs.

    The development proxy is not part of the production build. Before publishing, configure equivalent
    paths at your production gateway, or set the provider's public `baseUrl` and configure
    [credentialed CORS and cookies](/docs/modules/client/authentication) for separate origins.

    **Next.js**

    Wrap your existing Next.js configuration with `withRelkit` and merge these forwarding rules into
    its `rewrites`. Keep the other options and any existing rules:

    ```ts title="next.config.ts — merge with your existing config"
    /** Embeds the pulled fingerprint and forwards local browser requests to the backend. */
    import { withRelkit } from "@relkit/client/build/next";

    export default withRelkit({
      /** Keeps RPC and identity requests on the frontend origin.
       * @returns Local forwarding rules; replace their destinations for production. */
      async rewrites() {
        return [
          { source: "/rpc/:path*", destination: "http://127.0.0.1:3000/rpc/:path*" },
          { source: "/_relkit/:path*", destination: "http://127.0.0.1:3000/_relkit/:path*" },
        ];
      },
    });
    ```

    [`withRelkit`](/docs/api/client#withrelkit) embeds the pulled contract fingerprint.
    The [Next.js rewrites](https://nextjs.org/docs/app/api-reference/config/next-config-js/rewrites)
    forward `/rpc` calls and the `/_relkit/v1/client/identity` handshake to RELKIT. The browser stays
    on the frontend origin, so the provider needs no separate `baseUrl`.

    Keep RELKIT running as a separate backend on port `3000`; run Next.js on port `3002` in step 5.
    These rewrites also run in production: replace their local destinations with your backend's server URL
    before deploying. Static exports cannot run rewrites; use your gateway or a public backend URL with
    [credentialed CORS and cookies](/docs/modules/client/authentication) instead.

    If your app adds sign-in, forward its [auth mount](/docs/modules/client/authentication) too.

4. ## 4. Render the typed response

    `RelkitClientProvider` owns the connection and query cache; `useRoute` calls the generated
    operation with domain input. Both frameworks use the same component.

    **Vite**

    Replace the starter `src/App.tsx` with this component. Keep your existing entry point rendering `App`:

    ```tsx title="src/App.tsx"
    "use client";

    /** Renders the minimal backend's greeting through the pulled registry and a same-origin client provider. */
    import { RelkitClientProvider, useRelkitClient, useRoute } from "@relkit/client/react";

    /** Displays the typed Hello result after the provider resolves backend identity.
     * @returns Loading, connection failure, request failure, or the successful greeting. */
    export function Greeting() {
      const { status } = useRelkitClient();
      const greeting = useRoute("GET /hello", { input: { name: "RELKIT" } });

      if (status === "application-updated") {
        return <p role="alert">The API changed. Pull its contract and restart the frontend.</p>;
      }
      if (status === "error") return <p role="alert">Cannot connect to the API.</p>;
      if (greeting.isError) return <p role="alert">{greeting.error.message}</p>;
      if (greeting.isPending) return <p role="status">Loading greeting…</p>;
      return <h1>{greeting.data.message}</h1>;
    }

    /** Supplies the shared query cache and identity handshake to the frontend tree.
     * @returns The greeting inside a same-origin RELKIT provider. */
    export default function App() {
      return (
        <RelkitClientProvider>
          <Greeting />
        </RelkitClientProvider>
      );
    }
    ```

    **Next.js**

    Create `app/relkit-app.tsx` with this component. If your project uses `src/app`, place both files
    there instead. The [top-level `"use client"` directive](https://nextjs.org/docs/app/api-reference/directives/use-client)
    allows the provider and hooks to run inside a Client Component:

    ```tsx title="app/relkit-app.tsx"
    "use client";

    /** Renders the minimal backend's greeting through the pulled registry and a same-origin client provider. */
    import { RelkitClientProvider, useRelkitClient, useRoute } from "@relkit/client/react";

    /** Displays the typed Hello result after the provider resolves backend identity.
     * @returns Loading, connection failure, request failure, or the successful greeting. */
    export function Greeting() {
      const { status } = useRelkitClient();
      const greeting = useRoute("GET /hello", { input: { name: "RELKIT" } });

      if (status === "application-updated") {
        return <p role="alert">The API changed. Pull its contract and restart the frontend.</p>;
      }
      if (status === "error") return <p role="alert">Cannot connect to the API.</p>;
      if (greeting.isError) return <p role="alert">{greeting.error.message}</p>;
      if (greeting.isPending) return <p role="status">Loading greeting…</p>;
      return <h1>{greeting.data.message}</h1>;
    }

    /** Supplies the shared query cache and identity handshake to the frontend tree.
     * @returns The greeting inside a same-origin RELKIT provider. */
    export default function App() {
      return (
        <RelkitClientProvider>
          <Greeting />
        </RelkitClientProvider>
      );
    }
    ```

    Render it from your page. Keep your existing `app/layout.tsx`, including its `<html>` and `<body>`:

    ```tsx title="app/page.tsx"
    /** Renders the shared Hello example from a Next.js App Router server page. */
    import RelkitApp from "./relkit-app";

    /** Keeps provider hooks inside the imported Client Component.
     * @returns The greeting within the application's existing root layout. */
    export default function Page() {
      return <RelkitApp />;
    }
    ```

    Keep React hooks in the Client Component. For calls from a Node.js Server Component or Route Handler,
    use [`createClient`](/docs/modules/client/transports) with an explicit backend URL and the appropriate
    request identity. The `/server` and `/server/react` helpers require Bun; see
    [server rendering](/docs/modules/client/ssr) before selecting a server-side API.

    `greeting.data.message` is inferred from the backend function's output schema. No shared handwritten
    response interface is needed. The `GET /hello` selector identifies that route, but the client sends
    RPC over `/rpc`; it does not construct a REST query string. Raw handlers are not generated operations.

5. ## 5. Verify the page and contract

    From the frontend root:

    **Vite**

    ```sh title="Terminal — frontend root"
    bun run tsc --noEmit -p tsconfig.app.json
    bun run relkit client check http://127.0.0.1:3000 --out .relkit/generated
    bun run dev
    ```

    Open `http://127.0.0.1:5173`. Expect a loading message followed by **Hello, RELKIT!**. In the browser's
    Network panel, the identity request and the RPC request should succeed. Stop the backend and reload
    to check the connection error message.

    **Next.js**

    With your existing `dev` script running `next dev`, run:

    ```sh title="Terminal — frontend root"
    bun run tsc --noEmit
    bun run relkit client check http://127.0.0.1:3000 --out .relkit/generated
    bun run dev --hostname 127.0.0.1 --port 3002
    ```

    Open `http://127.0.0.1:3002`. Expect a loading message followed by **Hello, RELKIT!**. In the browser's
    Network panel, the identity and RPC requests should use port `3002` and succeed through the rewrites.
    Stop the backend and reload to check the connection error message.

    After changing the public backend contract, run `client pull` again, typecheck, and restart the
    frontend dev server so the plugin loads the new fingerprint. Use `client check` before a frontend
    build. A fingerprint mismatch blocks new typed work with `application-updated`; coordinate frontend
    and backend releases instead of bypassing the check.

## Connect another capability

Each module documents its backend declaration next to the corresponding frontend usage:

| Backend capability                    | Frontend entry point                                             | Continue with                                                                                             |
| ------------------------------------- | ---------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- |
| `defineRoute` / `defineServiceRoutes` | `useRoute`, `useRouteMutation`, or `createClient`                | [Call a defined route](/docs/modules/http/define#use-from-the-frontend)                                   |
| `streamOf` route output               | `useStream`                                                      | [Consume a response stream](/docs/modules/http/streaming#use-from-the-frontend)                           |
| `defineJob` with a client policy      | `useJobTrigger`, `useJobRun`, or the generated `client.jobs` API | [Trigger and observe a job](/docs/modules/jobs/quickstart#use-this-job-from-the-frontend)                 |
| Agent with a client policy            | `useAgent`                                                       | [Run an agent](/docs/modules/ai/agents#use-this-agent-from-the-frontend)                                  |
| `defineGraph` with a client policy    | `useAgent` with typed initial input and resume replies           | [Run and resume a graph](/docs/modules/ai/graphs-persistence#run-and-resume-this-graph-from-the-frontend) |
| `defineChannel` with browser access   | `useChannel`, `useRealtime`                                      | [Subscribe to a channel](/docs/modules/realtime#subscribe-from-the-frontend)                              |
| Better Auth mount                     | Better Auth's browser client and the RELKIT identity provider    | [Sign-in clients](/docs/modules/auth/clients)                                                             |

Buckets, caches, database models, and backend events have no direct generated browser API.
Expose an authorized function through a route when the frontend needs that operation. File uploads
use the route's [multipart HTTP contract](/docs/modules/http/uploads), rather than treating files
as ordinary RPC JSON.

## Related tasks

- [Add sign-in](/guides/auth/email-password-sign-in) before exposing private application data.
- [Upload product images](/guides/storage/upload-product-images) when the frontend sends files.

**[Typed Client module](/docs/modules/client)**

Choose transports, queries, mutations, streams, server rendering, and recovery APIs.

**[Client API reference](/docs/api/client)**

Look up signatures, options, return values, and import paths.

{/* Generated by apps/docs/scripts/generate.ts. Do not edit. */}

**Reference and source**

- [`@relkit/client` API](/docs/api/client)
- [`@relkit/routes` API](/docs/api/routes)
- [`@relkit/functions` API](/docs/api/functions)

- [`apps/docs/examples/frontend/app.tsx`](https://github.com/rel-kit/relkit/blob/main/apps/docs/examples/frontend/app.tsx)
- [`apps/docs/examples/frontend/vite.config.ts`](https://github.com/rel-kit/relkit/blob/main/apps/docs/examples/frontend/vite.config.ts)
- [`apps/docs/examples/frontend/next.config.ts`](https://github.com/rel-kit/relkit/blob/main/apps/docs/examples/frontend/next.config.ts)
- [`apps/docs/examples/frontend/next-page.tsx`](https://github.com/rel-kit/relkit/blob/main/apps/docs/examples/frontend/next-page.tsx)
