LangGraph.js Explained: Why Your Next AI Agent Needs a Graph, Not a Chain

# javascript# typescript# ai# webdev
LangGraph.js Explained: Why Your Next AI Agent Needs a Graph, Not a ChainProgramming Central

If you've spent any time building with LangChain, you know the power of chains. You string together...

If you've spent any time building with LangChain, you know the power of chains. You string together prompts, LLMs, and tools in a neat, linear fashion. It’s perfect for simple tasks like summarizing a document or answering a single question. But when you try to build an agent that can reason, plan, and correct itself, you hit a wall. Chains are linear; they execute once and terminate. They lack the ability to reflect.

To build truly autonomous agents, we need a computational structure that allows for loops, memory, and decision-making. Enter LangGraph.js.

This guide breaks down the architectural shift from linear chains to cyclical graphs, providing a "Hello World" multi-agent workflow you can run today.

The Shift: From Serverless Functions to Microservices

To understand LangGraph, we first need to look at the limitations of standard LangChain.

Chains: The Serverless Function Analogy

A standard LangChain Chain is like a Serverless Function (e.g., AWS Lambda or Vercel Edge Function). It is:

  • Stateless: It has no memory of previous runs.
  • Linear: It follows a single, unbranching path (A → B → C).
  • Ephemeral: It executes, returns a result, and dies.

This is great for isolated tasks, but terrible for agents. An agent needs to think, act, observe, and repeat.

LangGraph: The Microservice Architecture

LangGraph.js introduces the Cyclical Graph. It is like a Microservice Architecture orchestrated by a workflow engine (like AWS Step Functions).

  • Stateful: It maintains a shared state (memory) across the entire execution.
  • Cyclical: It allows loops. An agent can perform an action, check the result, and route back to a previous step to refine its approach.
  • Orchestrated: Nodes (services) communicate via Edges (pathways), governed by a shared state.

This structure enables the ReAct pattern (Reasoning and Acting), the foundation of modern autonomous agents.

Anatomy of a LangGraph: Nodes, Edges, and State

A LangGraph is built from three primitive components.

1. Nodes: The Units of Computation

A node is a function that performs work. In an agent context, a node could be:

  • An LLM Call: Reasoning about the next step.
  • A Tool Execution: Querying a database or calling an API.
  • A State Update: Logging an event or incrementing a counter.

2. Edges: The Flow of Control

Edges define which node runs next.

  • Direct Edges: A → B (Always go from A to B).
  • Conditional Edges: A → B or A → C (Decide the next step based on the current state).

3. The Shared State: The Single Source of Truth

This is the most critical concept. In a linear chain, data flows from component to component. In a cyclical graph, we need a central repository that persists across loops.

LangGraph typically uses Zod to define a state schema. This schema acts as a contract, ensuring every node knows what data is available. The state holds conversation history, tool results, and the agent's internal thoughts.

"Hello World": Building a Multi-Agent ReAct Loop

Let's build a simulation of a SaaS dashboard where two agents collaborate: a Reasoner and an Actor. The Reasoner analyzes the user request, and the Actor executes actions. They loop until the task is finished.

The Architecture

The graph follows a cyclical flow:

  1. Reasoner Node: Checks if an action is needed.
  2. Conditional Edge: Decides whether to act or finish.
  3. Actor Node: Executes the action (simulated).
  4. Loop: Returns to the Reasoner to process the result.

The Code

This code is self-contained. It uses @langchain/core for the graph primitives and zod for state validation.

/**
 * SaaS Multi-Agent Workflow: "Hello World" Example
 * Dependencies: @langchain/core, zod
 */

import { z } from "zod";
import { StateGraph, START, END } from "@langchain/core/graphs/state_graph";
import { Annotation } from "@langchain/core/langgraph";

// ==========================================
// 1. Define the Shared State Schema
// ==========================================

const GraphState = Annotation.Root({
    input: Annotation<string>({
        reducer: (curr, update) => update ?? curr,
        default: () => "",
    }),
    steps: Annotation<string[]>({
        reducer: (curr, update) => [...curr, update], // Accumulate history
        default: () => [],
    }),
    status: Annotation<string>({
        reducer: (curr, update) => update,
        default: () => "idle",
    }),
    shouldContinue: Annotation<boolean>({
        reducer: (curr, update) => update,
        default: () => false,
    }),
});

// ==========================================
// 2. Define Node Logic
// ==========================================

// The Reasoner: Analyzes input and decides if action is needed
const reasonerNode = (state: typeof GraphState.State): Partial<typeof GraphState.State> => {
    console.log("🤖 [Reasoner] Thinking...");
    const needsAction = state.input.toLowerCase().includes("search");

    return {
        steps: [...state.steps, `Reasoning: ${needsAction ? 'Action needed' : 'No action needed'}`],
        status: needsAction ? "acting" : "finished",
        shouldContinue: needsAction,
    };
};

// The Actor: Executes the external action
const actorNode = (state: typeof GraphState.State): Partial<typeof GraphState.State> => {
    console.log("⚡ [Actor] Executing action...");
    // Simulate an API call or tool execution
    const result = "Found result: 'LangGraph.js Documentation'";

    return {
        steps: [...state.steps, `Action: ${result}`],
        status: "reasoning", // Loop back to reasoning
        shouldContinue: true,
    };
};

// ==========================================
// 3. Define Conditional Edges
// ==========================================

const decideNextStep = (state: typeof GraphState.State): string => {
    if (state.status === "finished") return END;
    if (state.status === "acting") return "actor_node";
    return "reasoner_node";
};

// ==========================================
// 4. Build and Compile the Graph
// ==========================================

const workflow = new StateGraph(GraphState);

workflow.addNode("reasoner_node", reasonerNode);
workflow.addNode("actor_node", actorNode);

workflow.addEdge(START, "reasoner_node");

// The Cyclical Logic: Actor -> Decision -> (Actor or Reasoner)
workflow.addConditionalEdges("actor_node", decideNextStep);
workflow.addConditionalEdges("reasoner_node", (state) => 
    state.shouldContinue ? "actor_node" : END
);

const app = workflow.compile();

// ==========================================
// 5. Execution
// ==========================================

const runSaaSWorkflow = async () => {
    console.log("🚀 SaaS Dashboard: Initializing Agent Workflow...\n");

    const initialState = {
        input: "Search for latest LangGraph updates",
        steps: [],
        status: "idle",
        shouldContinue: false,
    };

    // Stream outputs for real-time updates
    const stream = await app.stream({ input: initialState.input });

    for await (const output of stream) {
        const nodeName = Object.keys(output)[0];
        const nodeState = output[nodeName];
        console.log(`--- Step: ${nodeName} ---`);
        console.log(`Status: ${nodeState.status}`);
        console.log(`Log: ${nodeState.steps[nodeState.steps.length - 1]}\n`);
    }

    console.log("✅ Workflow Complete.");
};

runSaaSWorkflow().catch(console.error);
Enter fullscreen mode Exit fullscreen mode

Key Technical Concepts Explained

Reducers: Managing Accumulation

Notice the reducer property in the GraphState definition:

steps: Annotation<string[]>({
    reducer: (curr, update) => [...curr, update],
    ...
})
Enter fullscreen mode Exit fullscreen mode

In a cyclical graph, state accumulates. Without a reducer, the steps array would be overwritten every time the graph loops. The reducer tells LangGraph how to merge new data into the existing state.

The Checkpointer: Persistence

LangGraph introduces the Checkpointer. This is an abstraction that saves the complete graph state to a database (Redis, SQL, etc.) after every node execution.

  • Why it matters: If your server crashes or you want to pause a long-running agent for human review, the Checkpointer allows you to resume exactly where you left off.

Streaming: Real-Time UX

In the code, we used app.stream().

  • The Pitfall: Using app.invoke() in a serverless environment (like Vercel) will cause timeouts if the agent loops too many times.
  • The Fix: stream() yields data after every node. In a Next.js app, you can pipe this directly to the frontend, creating a live "thought process" UI for your users.

Conclusion: Why This Matters for SaaS

The move from LangChain to LangGraph is the difference between a simple script and a robust application.

  1. Observability: Because the state is explicit and shared, you can log exactly what the agent was thinking at every step.
  2. Fault Tolerance: With a Checkpointer, your agents survive server restarts.
  3. Complex Logic: You can build complex workflows where agents hand off tasks to each other, retry failed steps, or wait for external events.

LangGraph.js turns AI agents from brittle prototypes into production-ready systems. By treating your agent as a graph of nodes and edges, you gain the control and reliability needed to build the next generation of AI applications.

The concepts and code demonstrated here are drawn directly from the comprehensive roadmap laid out in the book Autonomous Agents. Building Multi-Agent Systems and Workflows with LangGraph.js Amazon Link of the AI with JavaScript & TypeScript Series.
The ebook is also on Leanpub.com: https://leanpub.com/JSTypescriptAutonomousAgents.