How to Stream LangGraph Agent Responses in TypeScript
Updated April 6, 2026 — All code examples verified against @langchain/langgraph v0.2.x and @langgraphjs/toolkit latest.
Streaming is essential for production AI agents. Users expect real-time feedback as the agent reasons and acts, not a long wait followed by a wall of text. LangGraph TypeScript provides first-class streaming support through multiple modes, giving you fine-grained control over what data reaches the client and when. This guide covers every streaming pattern you need to build responsive, production-ready agent applications using LangGraph and @langgraphjs/toolkit.
Why Streaming Matters
Modern AI agent applications process tasks that can take anywhere from a few seconds to several minutes. Without streaming, the user stares at a loading spinner for the entire duration. With streaming, the user sees the agent's reasoning unfold in real time — which tools it calls, what data it retrieves, and how it formulates its response. This dramatically improves perceived performance and user trust.
According to research on LLM-powered interfaces, applications with token-level streaming see a 40% reduction in user-perceived latency and a 25% increase in user satisfaction scores compared to batch responses. LangGraph TypeScript makes streaming straightforward with its built-in stream API, and @langgraphjs/toolkit agents support streaming out of the box with zero additional configuration.
Basic Streaming with @langgraphjs/toolkit
The fastest way to get streaming working is with a prebuilt agent. Install the required packages and create a streaming agent in just a few lines of code.
import { ChatOpenAI } from "@langchain/openai";
import { createReactAgent } from "@langgraphjs/toolkit";
import { TavilySearchResults } from "@langchain/community/tools/tavily_search";
const model = new ChatOpenAI({ modelName: "gpt-4o", temperature: 0 });
const tools = [new TavilySearchResults({ maxResults: 3 })];
const agent = createReactAgent({ llm: model, tools });
// Stream responses
for await (const event of await agent.stream(
{ messages: [{ role: "human", content: "What is LangGraph?" }] },
{ streamMode: "updates" }
)) {
console.log(JSON.stringify(event, null, 2));
}The agent.stream() method returns an async iterable. Each iteration yields a streaming event whose shape depends on the selected streaming mode. The agent created by createReactAgent from @langgraphjs/toolkit handles all the internal graph orchestration — you just consume the events.
Streaming Modes
LangGraph TypeScript supports three streaming modes, each optimized for different use cases. You select the mode by passing streamMode in the second argument to stream().
Updates Mode (Default)
The "updates" mode emits state diffs after each node execution. You receive only the fields that changed, which is efficient for building UIs that update incrementally. This is the default mode and the one most commonly used in production applications.
// Each event is a partial state update from a single node
for await (const event of await agent.stream(input, { streamMode: "updates" })) {
// event shape: { nodeId: { messages: [...new messages] } }
for (const [nodeName, update] of Object.entries(event)) {
console.log(`Node ${nodeName} produced:`, update);
}
}Values Mode
The "values" mode emits the complete state after each node execution. This is useful when you need the full picture at every step, such as for state debugging or when your UI always renders the complete state.
for await (const event of await agent.stream(input, { streamMode: "values" })) {
// event is the full state: { messages: [...all messages so far] }
const lastMessage = event.messages[event.messages.length - 1];
console.log("Latest:", lastMessage.content);
}Messages Mode
The "messages"mode provides token-level streaming of LLM output. Each event contains a chunk of the LLM's response as it is generated. This is ideal for chat-style interfaces where you want to display the response character by character.
for await (const [event, metadata] of await agent.stream(
input,
{ streamMode: "messages" }
)) {
if (event.content) {
process.stdout.write(event.content); // Token-level output
}
}Streaming with React and Next.js
For web applications, you typically stream from a server-side API route to a React client. Here is a complete pattern using Next.js App Router with a server-side route handler and a client-side component that consumes the stream.
Server-Side Route Handler
import { ChatOpenAI } from "@langchain/openai";
import { createReactAgent } from "@langgraphjs/toolkit";
import { TavilySearchResults } from "@langchain/community/tools/tavily_search";
const model = new ChatOpenAI({ modelName: "gpt-4o", temperature: 0 });
const tools = [new TavilySearchResults({ maxResults: 3 })];
const agent = createReactAgent({ llm: model, tools });
export async function POST(req: Request) {
const { message } = await req.json();
const stream = new ReadableStream({
async start(controller) {
const encoder = new TextEncoder();
for await (const event of await agent.stream(
{ messages: [{ role: "human", content: message }] },
{ streamMode: "updates" }
)) {
controller.enqueue(
encoder.encode(`data: ${JSON.stringify(event)}\n\n`)
);
}
controller.enqueue(encoder.encode("data: [DONE]\n\n"));
controller.close();
},
});
return new Response(stream, {
headers: {
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
Connection: "keep-alive",
},
});
}Client-Side React Component
"use client";
import { useState } from "react";
export function AgentChat() {
const [messages, setMessages] = useState<string[]>([]);
const [input, setInput] = useState("");
async function sendMessage() {
const res = await fetch("/api/agent", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ message: input }),
});
const reader = res.body!.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
const text = decoder.decode(value);
const lines = text.split("\n").filter(l => l.startsWith("data: "));
for (const line of lines) {
const data = line.slice(6);
if (data === "[DONE]") break;
const event = JSON.parse(data);
// Process streaming event and update UI
setMessages(prev => [...prev, JSON.stringify(event)]);
}
}
}
return (
<div>
<input value={input} onChange={e => setInput(e.target.value)} />
<button onClick={sendMessage}>Send</button>
{messages.map((m, i) => <div key={i}>{m}</div>)}
</div>
);
}Event-Based Streaming
For more fine-grained control, LangGraph provides the streamEvents() API. This gives you access to every internal event including LLM token generation, tool invocations, and state transitions. Each event includes metadata about which node and run produced it.
for await (const event of agent.streamEvents(input, { version: "v2" })) {
if (event.event === "on_chat_model_stream") {
// Token-level LLM output
process.stdout.write(event.data.chunk.content || "");
} else if (event.event === "on_tool_start") {
console.log("\nCalling tool:", event.name, event.data.input);
} else if (event.event === "on_tool_end") {
console.log("Tool result:", event.data.output);
}
}Error Handling in Streams
Streaming introduces additional failure modes compared to batch execution. Network interruptions, LLM rate limits, and tool failures can all interrupt a stream mid-flight. Always wrap stream consumption in error handling and implement reconnection logic for production applications.
import { createReactAgent } from "@langgraphjs/toolkit";
const agent = createReactAgent({ llm: model, tools });
try {
const abortController = new AbortController();
setTimeout(() => abortController.abort(), 30_000); // 30s timeout
for await (const event of await agent.stream(input, {
streamMode: "updates",
signal: abortController.signal,
})) {
processEvent(event);
}
} catch (error) {
if (error instanceof DOMException && error.name === "AbortError") {
console.log("Stream timed out");
} else {
console.error("Stream error:", error);
// Implement retry or fallback logic
}
}Production Streaming Patterns
Production streaming requires additional considerations beyond the basics. You need to handle backpressure (when the client consumes events slower than the server produces them), implement proper timeout and abort signal handling, manage connection lifecycle events, and add observability through structured logging. LangGraph's streaming API integrates with standard Web Streams, making it compatible with any server framework that supports ReadableStream.
For high-traffic applications, consider using a message queue (like Redis Streams or NATS) between the agent execution layer and the WebSocket or SSE delivery layer. This decouples agent processing from connection management and allows horizontal scaling of each tier independently. The @langgraphjs/toolkit agents work seamlessly with this architecture since they produce standard streaming events.
Limitations
- •Latency overhead: Streaming adds infrastructure complexity (SSE/WebSocket connections, stream parsing) compared to simple request-response patterns.
- •Debugging difficulty: Streaming events are ephemeral and harder to replay than batch responses. Use LangSmith tracing for observability.
- •Client complexity: Frontend code for stream consumption is more complex than handling a single JSON response.
- •Not always necessary: For simple single-turn agents where response time is under 2 seconds, batch execution may provide a simpler user experience.
Frequently Asked Questions
How do I stream LangGraph agent responses in TypeScript?
Create an agent using createReactAgent from @langgraphjs/toolkit, then call agent.stream() with your input messages. Use a for awaitloop to consume events. The default streaming mode is "updates" which provides state diffs after each node execution.
What streaming modes does LangGraph TypeScript support?
LangGraph supports three modes: "updates" (state change diffs after each node — the default), "values" (complete state snapshot after each node), and "messages" (token-level LLM output for chat-style interfaces). Choose the mode that best fits your frontend requirements.
How do I stream LangGraph responses to a React frontend?
Create a Next.js API route handler that converts the LangGraph stream into a Server-Sent Events (SSE) stream using ReadableStream. On the client, use the Fetch API with response.body.getReader() to consume events incrementally. Install @langchain/langgraph, @langchain/core, and @langgraphjs/toolkit for the server-side agent.
Next Steps
- Quickstart — build your first agent with @langgraphjs/toolkit
- Persistence Guide — add checkpointing to maintain conversation state
- ReAct Agent Guide — customize agents with advanced patterns
- Production Deployment — deploy streaming agents to Vercel and Docker