Pipeforge is a full-stack visual pipeline builder that lets developers construct, validate, and execute data-flow graphs (DAGs) through a drag-and-drop canvas. It features nine typed nodes — including an LLM node backed by OpenRouter — dual cycle detection across frontend and backend, and a TypeScript + Vite codebase deployed on Vercel and Render. Built as a portfolio project, it demonstrates end-to-end ownership of a non-trivial graph-based tool from algorithm design to production deployment.

Pipeforge is a visual DAG (Directed Acyclic Graph) pipeline builder — a browser-based tool where developers construct data-processing workflows by wiring together typed nodes on an interactive canvas. Pipelines are validated in real time on both the client and server, and can run live LLM inference via an integrated OpenRouter node — all without leaving the browser.
Building data pipelines typically means writing configuration files or code that is hard to reason about visually. Developers working on workflow automation, ETL jobs, or LLM chains often lack a fast, lightweight tool to sketch and validate a pipeline's structure without committing to a full orchestration platform. Pipeforge fills that gap: a focused graph editor with real validation logic, AI inference, and a clean monorepo architecture.
The project is a monorepo with two independently deployable services:
Frontend (frontend/): A React 18 SPA built with Vite and written entirely in TypeScript.
ReactFlow 11 drives the canvas; Zustand (with zundo middleware) manages pipeline state with
50-step undo/redo. Nodes are composed via a shared NodeBase pattern so all nine types share
consistent handle and layout logic while exposing type-specific controls. Pipelines can be
saved to and loaded from localStorage.
Backend (backend/): A stateless FastAPI service that handles two concerns — structural
validation (POST /pipelines/parse, returns DAG metadata including a cycle-detection flag) and
LLM inference (POST /pipelines/run, proxies to OpenRouter via httpx with streamed responses).
All request/response contracts are enforced with Pydantic v2 models.
Cycle detection runs an adjacency-list DFS in both TypeScript (for instant canvas feedback) and Python (as the authoritative server-side check) — the same algorithm in both runtimes. The frontend implementation is covered by a Vitest test suite.
| Technology | Role | Why |
|---|---|---|
| React 18 | UI framework | Component model maps cleanly to the node-per-component graph paradigm |
| ReactFlow 11 | Canvas / graph engine | Purpose-built for node editors; handles pan, zoom, and edge routing out of the box |
| Zustand | State management | Minimal boilerplate for a single-page tool that doesn't need Redux complexity |
| zundo | Undo/redo middleware | Plugs into Zustand to add 50-step history with no manual snapshot logic |
| TypeScript | Type safety | Catches handle/node contract mismatches at compile time across 15+ source files |
| Vite | Build tooling | Fast HMR and code splitting; replaces CRA with a leaner, production-ready pipeline |
| Vitest | Test runner | Native Vite integration — runs cycle detection unit tests with zero config |
| FastAPI | REST API | Async-native, automatic OpenAPI docs, first-class Pydantic integration |
| Pydantic v2 | Schema validation | Enforces node/edge contracts at the API boundary with Python type hints |
| httpx | HTTP client | Async-capable client used to proxy and stream OpenRouter LLM responses |
| Uvicorn | ASGI server | Production-grade server for FastAPI with minimal configuration |
| Python 3.10 | Backend runtime | Match-statement support; broad hosting compatibility on Render |
| Vercel | Frontend hosting | Zero-config deploys from git with edge CDN and preview URLs |
| Render | Backend hosting | Simple containerless Python service hosting with env var management |
1. Dual cycle detection
Cycle detection needed to work on both sides of the stack — instantly in the browser for UX, and
authoritatively on the server for correctness. Both implementations use the same adjacency-list
DFS with a recursion stack, keeping them algorithmically consistent. The TypeScript version is
extracted into its own module (detectCycles.ts) and covered by Vitest unit tests.
2. Dynamic handles on Text nodes
Text nodes support template variables ({{ varName }}) that dynamically generate new input
handles as the user types. Wiring these into ReactFlow's handle system — which expects a stable
handle set at render time — required rebuilding handle lists reactively from node content and
syncing state through Zustand without triggering full graph re-renders.
3. LLM node with streamed responses
The LLM node calls OpenRouter with a configurable model selector and separate system/user prompt
fields. On the backend, httpx streams the response back to the client so output appears
progressively. Handling streaming in a stateless FastAPI endpoint while keeping the frontend
result modal reactive required careful use of FastAPI's StreamingResponse and React state
updates.
4. Safe math evaluation
The original Math node used eval() — fast to build, unsafe to ship. It was replaced with
mathjs, a sandboxed expression evaluator that supports the same arithmetic operators and shows a
live result preview without exposing arbitrary code execution.
5. Vite + TypeScript migration
The entire frontend was migrated from Create React App (JavaScript) to Vite + TypeScript. All 15+
source files were converted, strict tsconfig rules were applied, and the build pipeline was
updated. Code splitting was configured in vite.config.ts to keep initial bundle size small.
6. Production configuration
API base URL is driven by VITE_API_URL, allowed CORS origins by ALLOWED_ORIGINS, and the
OpenRouter key by OPENROUTER_API_KEY — all environment variables so the same build artefacts
work in dev and production without code changes. A Makefile provides make dev, make install,
and make test shortcuts for the monorepo.
any in production source.