# React (Vite / CRA)

Use Courier with React and Vite or Create React App via createProxyQueryCaller and a separate backend proxy.

For React apps built with Vite or Create React App, Courier provides `createProxyQueryCaller` for type-safe data fetching with React Query. Since these apps don't have a built-in server, you need a separate backend (Express, Hono, or Fastify) to host the proxy route.

## In one command

```bash
npx @noverax/create-courier
```

In a React app Vite builds, it sets Courier up in direct mode — React Query hooks that call your API straight from the browser, and Studio's plugin — with a typed example API, its mock, and a page of its own at `/courier.html`. For a backend proxy, as this page sets up, add one of the server guides. See [Create Courier](/docs/create-courier).

## Installation

```bash
npm install @noverax/courier @noverax/courier-react @tanstack/react-query
```

## Define your API sources

Create the API definition the backend proxy and Studio share. The React app never imports it: it takes the routes Courier writes from it, so the definition stays out of the browser bundle.

```ts title="shared/sources.ts"
import { courier, defineApi, defineGroup } from "@noverax/courier/define"

const { endpoint } = courier()

type Product = { id: number; name: string; price: number }
type ProductList = { data: Product[]; total: number }

const products = defineGroup({
  prefix: "products",
  // A page may call these through the proxy; an endpoint no mark opens stays the server's
  expose: true,
  endpoints: {
    getAll: endpoint.get("").variables<{ page?: number; category?: string }>().response<ProductList>(),
    getById: endpoint.get(":id").variables<{ id: number }>().response<Product>(),
    create: endpoint.post("").variables<{ name: string; price: number }>().response<Product>(),
  },
})

export const api = defineApi({
  baseURL: process.env.API_URL ?? "http://localhost:4000",
  groups: { products },
})

export const sources = { myApi: api }
```

## Step 1: Set up the proxy backend

You need a backend server to host the Courier proxy route. Here is an example using Express:

```ts title="server/index.ts"
import express from "express"
import cors from "cors"
import { registerSources } from "@noverax/courier"
import { courierHandler } from "@noverax/courier-express"
import { sources } from "../shared/sources"

registerSources(sources)

const app = express()

app.use(cors({ origin: "http://localhost:5173", credentials: true }))

// No body parser before this line: Courier reads the raw body itself, and a
// global `express.json()` would consume it first.

app.all("/api/request", courierHandler())

app.listen(3001, () => {
  console.log("Proxy server running on http://localhost:3001")
})
```

> **Choose your backend**
>
> You can use any supported adapter: [Express](/docs/frameworks/express), [Hono](/docs/frameworks/hono), [Fastify](/docs/frameworks/fastify), or [NestJS](/docs/frameworks/nestjs). The proxy route is framework-agnostic — only the adapter differs.

## Step 2: Create the query caller

List your sources in `courier.config.ts`. From the endpoints they mark `.expose()`, Courier writes the routes the app's caller takes into `courier.routes.ts` beside it — Studio whenever it loads the config, or `npx @noverax/courier-cli routes` now; see [Exposing Endpoints](/docs/proxy-mode/expose):

```ts title="courier.config.ts"
import { defineConfig } from "@noverax/courier/config"
import { sources } from "./shared/sources"

export default defineConfig({ sources })
```

```ts title="src/lib/query.ts"
import { createProxyQueryCaller } from "@noverax/courier-react"
import { myApiRoutes } from "../../courier.routes"

export const q = createProxyQueryCaller(myApiRoutes, {
  proxyPath: { http: "http://localhost:3001/api/request" },
})
```

## Step 3: Set up QueryClientProvider

Wrap your app with the React Query provider:

```tsx title="src/main.tsx"
import { StrictMode } from "react"
import { createRoot } from "react-dom/client"
import { QueryClient, QueryClientProvider } from "@noverax/courier-react"
import App from "./App"

const queryClient = new QueryClient()

createRoot(document.getElementById("root")!).render(
  <StrictMode>
    <QueryClientProvider client={queryClient}>
      <App />
    </QueryClientProvider>
  </StrictMode>
)
```

## Step 4: Use in components

```tsx title="src/components/ProductList.tsx"
import { q } from "@/lib/query"

export function ProductList() {
  const { data, isLoading, error } = q.products.useQuery("getAll", {
    variables: { page: 1, category: "electronics" },
  })

  if (isLoading) return <p>Loading...</p>
  if (error) return <p>Error: {error.message}</p>

  return (
    <div>
      <h2>Products ({data?.total})</h2>
      <ul>
        {data?.data.map((product) => (
          <li key={product.id}>
            {product.name} — ${product.price}
          </li>
        ))}
      </ul>
    </div>
  )
}
```

```tsx title="src/components/CreateProduct.tsx"
import { q } from "@/lib/query"
import { useState } from "react"
import { useQueryClient } from "@noverax/courier-react"

export function CreateProduct() {
  const queryClient = useQueryClient()
  const [name, setName] = useState("")
  const [price, setPrice] = useState("")

  const { mutate, isPending } = q.products.useMutation("create", {
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ["myApi", "products:getAll"] })
      setName("")
      setPrice("")
    },
  })

  return (
    <form
      onSubmit={(e) => {
        e.preventDefault()
        mutate({ name, price: Number(price) })
      }}
    >
      <input
        value={name}
        onChange={(e) => setName(e.target.value)}
        placeholder="Product name"
        required
      />
      <input
        value={price}
        onChange={(e) => setPrice(e.target.value)}
        type="number"
        placeholder="Price"
        required
      />
      <button type="submit" disabled={isPending}>
        {isPending ? "Creating..." : "Add Product"}
      </button>
    </form>
  )
}
```

> **CORS and cookies**
>
> Make sure your proxy backend has CORS configured to allow requests from your React app's origin. If your backend uses cookies for auth, set `credentials: true` in the CORS config and `credentials: "include"` in the caller defaults, and name `cookie` and `set-cookie` on the source or the endpoints that carry the session — see [Cookies](/docs/proxy-mode/api-route#cookies).

## Project structure

```text title="Project structure"
courier.config.ts           # defineConfig({ sources })
courier.routes.ts           # written by Studio from the .expose() marks
shared/
  sources.ts                # courier + defineGroup + defineApi
server/
  index.ts                  # Express/Hono/Fastify with courierHandler
src/
  lib/query.ts              # createProxyQueryCaller(myApiRoutes)
  components/
    ProductList.tsx         # q.products.useQuery usage
    CreateProduct.tsx       # q.products.useMutation usage
  main.tsx                  # QueryClientProvider setup
```

## Environment variables

Use a `.env` file to configure the proxy URL for different environments:

```bash title=".env"
VITE_PROXY_URL=http://localhost:3001/api/request
```

```ts title="src/lib/query.ts"
import { createProxyQueryCaller } from "@noverax/courier-react"
import { myApiRoutes } from "../../courier.routes"

export const q = createProxyQueryCaller(myApiRoutes, {
  proxyPath: { http: import.meta.env.VITE_PROXY_URL },
})
```
