# 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]
### Install dependencies \[#install-dependencies]
### 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 });
};
```
### 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 (
);
}
```
### Mount the component \[#mount-the-component]
```tsx title="@/app/page.tsx"
import { MyAssistant } from "@/components/MyAssistant";
export default function Home() {
return (
);
}
```
### Set environment variables \[#set-environment-variables]
Same `.env.local` shape as the template path [above](#from-the-template).
### 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.
## 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]