Skip to content

Framework GuidesReact (Vite / CRA)

React (Vite / CRA)

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

2 min readproxy mode

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.

Installation

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

Define your API sources

Create a shared API definition. This file is used by both the React app and the backend proxy:

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",
  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:

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, Hono, Fastify, or NestJS. The proxy route is framework-agnostic — only the adapter differs.

Step 2: Create the query caller

src/lib/query.ts
import { createProxyQueryCaller } from "@noverax/courier-react"
import type { api } from "../../shared/sources"

export const q = createProxyQueryCaller<typeof api>("myApi", {
  proxyPath: { http: "http://localhost:3001/api/request" },
})

Step 3: Set up QueryClientProvider

Wrap your app with the React Query provider:

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

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>
  )
}
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.

Project structure

Project structure
shared/
  sources.ts                # courier + defineGroup + defineApi (shared)
server/
  index.ts                  # Express/Hono/Fastify with courierHandler
src/
  lib/query.ts              # createProxyQueryCaller<typeof api>("myApi")
  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:

.env
VITE_PROXY_URL=http://localhost:3001/api/request
src/lib/query.ts
import { createProxyQueryCaller } from "@noverax/courier-react"
import type { api } from "../../shared/sources"

export const q = createProxyQueryCaller<typeof api>("myApi", {
  proxyPath: { http: import.meta.env.VITE_PROXY_URL },
})