# Quickstart
URL: /docs/runtimes/langgraph/quickstart

From-template and manual setup paths to a working LangGraph chat.

Two paths to a running chat against a LangGraph Cloud server. The template is fastest; the manual path is what you adapt when integrating into an existing project.

## From the template \[#from-the-template]

```sh
npx create-assistant-ui@latest -t langgraph my-app
cd my-app
```

Set environment variables:

```sh title=".env.local"
# LANGCHAIN_API_KEY=your_api_key       # production
# LANGGRAPH_API_URL=your_api_url       # production
NEXT_PUBLIC_LANGGRAPH_API_URL=your_api_url           # development (no API key required)
NEXT_PUBLIC_LANGGRAPH_ASSISTANT_ID=your_graph_id
```

```sh
npm run dev
```

Skip ahead to [streaming](/docs/runtimes/langgraph/streaming) to start adding features.

## Manual setup in an existing project \[#manual-setup-in-an-existing-project]

<Steps>
  <Step>
    ### Install dependencies \[#install-dependencies]

    <InstallCommand npm="[&#x22;@assistant-ui/react&#x22;, &#x22;@assistant-ui/react-langgraph&#x22;, &#x22;@langchain/langgraph-sdk&#x22;]" />
  </Step>

  <Step>
    ### Create the LangGraph client helper \[#create-the-langgraph-client-helper]

    ```ts title="@/lib/chatApi.ts"
    import { Client } from "@langchain/langgraph-sdk";

    export const createClient = () => {
      const apiUrl =
        process.env["NEXT_PUBLIC_LANGGRAPH_API_URL"] ||
        (typeof window !== "undefined"
          ? new URL("/api", window.location.href).href
          : "/api");
      return new Client({ apiUrl });
    };
    ```
  </Step>

  <Step>
    ### Build the assistant component \[#build-the-assistant-component]

    ```tsx title="@/components/MyAssistant.tsx"
    "use client";

    import { useMemo } from "react";
    import { Thread } from "@/components/assistant-ui/thread";
    import { AssistantRuntimeProvider } from "@assistant-ui/react";
    import {
      unstable_createLangGraphStream,
      useLangGraphRuntime,
      type LangChainMessage,
    } from "@assistant-ui/react-langgraph";

    import { createClient } from "@/lib/chatApi";

    const ASSISTANT_ID = process.env["NEXT_PUBLIC_LANGGRAPH_ASSISTANT_ID"]!;

    export function MyAssistant() {
      const client = useMemo(() => createClient(), []);
      const stream = useMemo(
        () =>
          unstable_createLangGraphStream({
            client,
            assistantId: ASSISTANT_ID,
          }),
        [client],
      );

      const runtime = useLangGraphRuntime({
        unstable_allowCancellation: true,
        stream,
        create: async () => {
          const { thread_id } = await client.threads.create();
          return { externalId: thread_id };
        },
        load: async (externalId) => {
          const state = await client.threads.getState<{
            messages: LangChainMessage[];
          }>(externalId);
          return {
            messages: state.values.messages,
            interrupts: state.tasks[0]?.interrupts,
          };
        },
      });

      return (
        <AssistantRuntimeProvider runtime={runtime}>
          <Thread />
        </AssistantRuntimeProvider>
      );
    }
    ```
  </Step>

  <Step>
    ### Mount the component \[#mount-the-component]

    ```tsx title="@/app/page.tsx"
    import { MyAssistant } from "@/components/MyAssistant";

    export default function Home() {
      return (
        <main className="h-dvh">
          <MyAssistant />
        </main>
      );
    }
    ```
  </Step>

  <Step>
    ### Set environment variables \[#set-environment-variables]

    Same `.env.local` shape as the template path [above](#from-the-template).
  </Step>

  <Step>
    ### Set up UI components \[#set-up-ui-components]

    Follow the [UI Components guide](/docs/ui/thread) to wire up the Thread, composer, and supporting primitives.
  </Step>
</Steps>

## Production proxy backend \[#production-proxy-backend]

For development, the client above hits LangGraph Cloud directly using `NEXT_PUBLIC_LANGGRAPH_API_URL`. For production, proxy through your own backend so your API key never reaches the browser. Limit the proxy to the endpoints you actually need.

```ts title="@/app/api/[...path]/route.ts"
import { NextRequest, NextResponse } from "next/server";

export const runtime = "edge";

function getCorsHeaders() {
  return {
    "Access-Control-Allow-Origin": "*",
    "Access-Control-Allow-Methods": "GET, POST, PUT, PATCH, DELETE, OPTIONS",
    "Access-Control-Allow-Headers": "*",
  };
}

async function handleRequest(req: NextRequest, method: string) {
  try {
    const path = req.nextUrl.pathname.replace(/^\/?api\//, "");
    const url = new URL(req.url);
    const searchParams = new URLSearchParams(url.search);
    searchParams.delete("_path");
    searchParams.delete("nxtP_path");
    const queryString = searchParams.toString()
      ? `?${searchParams.toString()}`
      : "";

    const options: RequestInit = {
      method,
      headers: { "x-api-key": process.env["LANGCHAIN_API_KEY"] ?? "" },
      signal: req.signal,
    };
    if (["POST", "PUT", "PATCH"].includes(method)) {
      options.body = await req.text();
    }

    const res = await fetch(
      `${process.env["LANGGRAPH_API_URL"]}/${path}${queryString}`,
      options,
    );
    const headers = new Headers(res.headers);
    headers.delete("content-encoding");
    headers.delete("content-length");
    headers.delete("transfer-encoding");
    for (const [key, value] of Object.entries(getCorsHeaders())) {
      headers.set(key, value);
    }
    return new NextResponse(res.body, {
      status: res.status,
      statusText: res.statusText,
      headers,
    });
  } catch (e: unknown) {
    if (e instanceof Error) {
      const typedError = e as Error & { status?: number };
      return NextResponse.json(
        { error: typedError.message },
        { status: typedError.status ?? 500 },
      );
    }
    return NextResponse.json({ error: "Unknown error" }, { status: 500 });
  }
}

export const GET = (req: NextRequest) => handleRequest(req, "GET");
export const POST = (req: NextRequest) => handleRequest(req, "POST");
export const PUT = (req: NextRequest) => handleRequest(req, "PUT");
export const PATCH = (req: NextRequest) => handleRequest(req, "PATCH");
export const DELETE = (req: NextRequest) => handleRequest(req, "DELETE");
export const OPTIONS = () =>
  new NextResponse(null, { status: 204, headers: getCorsHeaders() });
```

With this route in place, drop `NEXT_PUBLIC_LANGGRAPH_API_URL` from production env vars; the client helper falls back to the same-origin `/api` path. Set `LANGCHAIN_API_KEY` and `LANGGRAPH_API_URL` server-side instead.

## Next \[#next]

<Cards>
  <Card title="Streaming" description="Event handlers, message metadata, message conversion." href="/docs/runtimes/langgraph/streaming" />

  <Card title="Generative UI" description="Structured UI components emitted by your graph." href="/docs/runtimes/langgraph/generative-ui" />

  <Card title="Interrupts" description="Interrupt persistence and checkpoint-based message editing." href="/docs/runtimes/langgraph/interrupts" />
</Cards>
