LangChain.js: Building LLM Applications with TypeScript

Lesson 2 of 6

Chains with LCEL: Composing Runnables in TypeScript

LCEL (LangChain Expression Language) composes in TypeScript the same way it does in Python, except instead of the | operator (which TypeScript doesn't let you overload), LangChain.js uses an explicit .pipe() method to chain Runnables together.

Building a chain with .pipe()

TypeScript

This needs a real API key, so it's read-only here. prompt.pipe(model).pipe(parser) reads left to right, exactly like Python's prompt | model | parser: the input object flows into prompt, its output flows into model, and the model's response flows into parser, which extracts a plain string. result is typed as string, not a message object you'd need to unwrap.

Batching

TypeScript

Also read-only here, since it calls the model. Because chain is itself a Runnable, it gets .batch() (many inputs) and .stream() (token-by-token output) automatically, the same three methods every piece in the pipeline already had.

Why types matter here

Each piece in a .pipe() chain is generically typed by its input and output, ChatPromptTemplate outputs a message type that ChatOpenAI accepts as input, and StringOutputParser narrows that model's response down to string. If you tried to pipe two incompatible pieces together, TypeScript's compiler catches the mismatch before you ever run the code, one of the concrete advantages of building LLM chains in a typed language.

NOTE

.pipe() and Python's | do exactly the same thing: build a new Runnable from smaller ones. The syntax differs because operator overloading isn't available in TypeScript, the composition model underneath is identical.

📝 LCEL in TypeScript Quiz

Passing score: 70%
  1. 1.In LangChain.js, what replaces Python's | operator for composing a chain?

  2. 2.Because a LangChain.js chain is itself a Runnable, it automatically gets .batch() and .stream() without extra code.

  3. 3.TypeScript's compiler can catch a mismatch between two incompatible pieces piped together, because each Runnable is generically typed by its input and ____ type.