# Vue

Use Courier with Vue via createProxyCaller in composables with ref/reactive patterns.

For Vue apps (with Vite), Courier provides `createProxyCaller` for type-safe API calls. Since Vue 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 Vue app Vite builds, it sets Courier up in direct mode — a client that calls 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`. In Nuxt it sets up the proxy route instead: see [Nuxt](/docs/frameworks/nuxt). See [Create Courier](/docs/create-courier).

## Installation

```bash
npm install @noverax/courier
```

## Define your API sources

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

const { endpoint } = courier()

type Task = { id: number; title: string; completed: boolean }

const tasks = defineGroup({
  prefix: "tasks",
  // A page may call these through the proxy; an endpoint no mark opens stays the server's
  expose: true,
  endpoints: {
    getAll: endpoint.get("").variables<{ status?: "all" | "active" | "completed" }>().response<Task[]>(),
    getById: endpoint.get(":id").variables<{ id: number }>().response<Task>(),
    create: endpoint.post("").variables<{ title: string }>().response<Task>(),
    toggle: endpoint.patch(":id").variables<{ id: number; completed: boolean }>().response<Task>(),
    remove: endpoint.del(":id").variables<{ id: number }>().response<null>(),
  },
})

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

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 a composable

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). The app imports the routes, never the definition, which stays out of its bundle:

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

export default defineConfig({ sources })
```

```ts title="src/composables/useCaller.ts"
import { createProxyCaller } from "@noverax/courier"
import { myApiRoutes } from "../../courier.routes"

export const caller = createProxyCaller(myApiRoutes, {
  proxyPath: { http: import.meta.env.VITE_PROXY_URL ?? "http://localhost:3001/api/request" },
})
```

## Step 3: Use in components

### Fetching data with ref

```vue title="src/components/TaskList.vue"
<script setup lang="ts">
import { ref, onMounted } from "vue"
import { caller } from "@/composables/useCaller"

type Task = { id: number; title: string; completed: boolean }

const tasks = ref<Task[]>([])
const isLoading = ref(true)
const error = ref<string | null>(null)

async function fetchTasks(status?: "all" | "active" | "completed") {
  isLoading.value = true
  error.value = null
  try {
    tasks.value = await caller.tasks.getAll({ status })
  } catch (e) {
    error.value = e instanceof Error ? e.message : "Failed to load tasks"
  } finally {
    isLoading.value = false
  }
}

onMounted(() => fetchTasks())
</script>

<template>
  <div>
    <div>
      <button @click="fetchTasks('all')">All</button>
      <button @click="fetchTasks('active')">Active</button>
      <button @click="fetchTasks('completed')">Completed</button>
    </div>

    <p v-if="isLoading">Loading...</p>
    <p v-else-if="error">{{ error }}</p>
    <ul v-else>
      <li v-for="task in tasks" :key="task.id">
        <span :class="{ completed: task.completed }">{{ task.title }}</span>
      </li>
    </ul>
  </div>
</template>
```

### Mutations with reactive state

```vue title="src/components/CreateTask.vue"
<script setup lang="ts">
import { reactive } from "vue"
import { caller } from "@/composables/useCaller"

const emit = defineEmits<{ created: [] }>()

const form = reactive({
  title: "",
  isSubmitting: false,
  error: null as string | null,
})

async function handleSubmit() {
  form.isSubmitting = true
  form.error = null
  try {
    await caller.tasks.create({ title: form.title })
    form.title = ""
    emit("created")
  } catch (e) {
    form.error = e instanceof Error ? e.message : "Failed to create task"
  } finally {
    form.isSubmitting = false
  }
}
</script>

<template>
  <form @submit.prevent="handleSubmit">
    <input v-model="form.title" placeholder="New task" required />
    <button type="submit" :disabled="form.isSubmitting">
      {{ form.isSubmitting ? "Creating..." : "Add Task" }}
    </button>
    <p v-if="form.error" class="error">{{ form.error }}</p>
  </form>
</template>
```

### Composable with auto-fetch

For reusable data fetching, create a generic composable:

```ts title="src/composables/useCourierQuery.ts"
import { ref, onMounted, type Ref } from "vue"
import { caller } from "./useCaller"

type Caller = typeof caller

export function useCourierQuery<T>(
  fn: (c: Caller) => Promise<T>
) {
  const data = ref<T | null>(null) as Ref<T | null>
  const isLoading = ref(true)
  const error = ref<string | null>(null)

  async function refetch() {
    isLoading.value = true
    error.value = null
    try {
      data.value = await fn(caller)
    } catch (e) {
      error.value = e instanceof Error ? e.message : "Request failed"
    } finally {
      isLoading.value = false
    }
  }

  onMounted(refetch)

  return { data, isLoading, error, refetch }
}
```

> **@tanstack/vue-query**
>
> For production apps with advanced caching, background refetching, and optimistic updates, use `@tanstack/vue-query`. Courier callers return standard promises, so they integrate directly:
>
> ```ts
> import { useQuery } from "@tanstack/vue-query"
> import { caller } from "@/composables/useCaller"
>
> const { data, isLoading } = useQuery({
>   queryKey: ["tasks"],
>   queryFn: () => caller.tasks.getAll({ status: "active" }),
> })
> ```

## Project structure

```text title="Project structure"
courier.config.ts             # defineConfig({ sources })
courier.routes.ts             # written by Studio from the .expose() marks
shared/
  sources.ts                  # defineApi + defineGroup + endpoint
server/
  index.ts                    # Express/Hono/Fastify proxy backend
src/
  composables/
    useCaller.ts              # createProxyCaller(myApiRoutes)
    useCourierQuery.ts        # optional generic fetch composable
  components/
    TaskList.vue              # data fetching with ref
    CreateTask.vue            # mutations with reactive
```
