I Built a Visual ETL Pipeline Into a Browser Table Editor
VLOOKUPs are a bad sign. Not because the function itself is wrong, but because reaching for VLOOKUP usually means you're doing something manually that should be a pipeline.
Two tables. A shared key. You want to join them. In a proper data environment, that's one SQL statement. In most people's day-to-day workflow, it's a fragile Excel formula they'll have to rebuild from scratch next month when the column order changes.
This is what I was trying to solve when I built the Node Editor tab into TAFNE.
The Problem It's Solving
TAFNE is a browser-based table editor. You paste in a CSV, ASCII table, HTML, JSON, or Markdown, and you get an interactive table you can edit, reshape, and export in six formats. That covers a lot of ground for working with a single dataset.
But data rarely lives in a single dataset.
You have a "Sales Log" sheet. You have an "HR Staff" sheet. Finance wants a report that joins them on Rep ID. You have a local contact list and you want to enrich it with live data from a public API. You have quarterly exports from two different sources and you need to stack them into one table before the pivot.
Without a way to connect datasets, every one of these tasks becomes a manual copy-paste job.
The Node Editor gives TAFNE a visual pipeline builder, drag nodes onto a canvas, wire them together, click Run, and get a table.
What Nodes Are Available
The node palette has five types:
Sheet: takes any existing TAFNE sheet and makes it available as a data source in the graph. If you've loaded data in Table Mode or received output from Lab Mode, it shows up here.
API: makes a GET request to a URL you specify, extracts data from a JSON path you define, and makes the result available as rows. No auth headers in the current version, so it works with open APIs. The restriction is intentional for now, it keeps the tool simple and avoids scope creep around credential management.
Filter: keeps only rows that match a condition. Ten operators: equals, not equals, greater than, less than, contains, starts with, ends with, is empty, is not empty, and the range-aware versions.
Formula: adds a computed column. You define the expression using column names in curly braces: {HourlyRate} * {HoursWorked}. The expression evaluator is a structured picker, not free-form JavaScript. Multiply, divide, add, subtract, concatenate, conditional. No eval call involved.
Join: merges two inputs. Three modes: Stack (vertical concatenation, like UNION ALL in SQL), Lateral (side-by-side by row index), and Inner Join on key columns (like a standard SQL INNER JOIN).
An Example Pipeline
Here's one I ran while testing: the Pokémon ETL.
[API: pokeapi.co/api/v2/pokemon?limit=50] → [Filter: name contains 'b'] → [Formula: name → Pokemon_Name] → Build Table
- API node: fetch 50 Pokémon from PokéAPI, extract the
resultsarray - Filter node: keep only entries where name contains "b", bulbasaur, butterfree, blastoise, etc.
- Formula node: pass the name through to a labeled column
Pokemon_Name - Build Table: output lands as a new TAFNE sheet
Total setup time: about 90 seconds. This runs the same way every time without touching a single cell manually.
The Graph Model
Under the hood, the Node Editor is a directed acyclic graph. Each node is a step. The wires are directed edges. When you click Run, a topological sort determines the execution order and processes nodes from source to output.
Each node type has a handler function. Sheet nodes load from the TAFNE sheet store. API nodes fetch and parse the response. Filter, Formula, and Join nodes operate on arrays of row objects, plain JavaScript objects where keys are column names.
The runtime result map is keyed by node ID. When a Join node executes, it looks up the left input result and the right input result by the IDs of whatever nodes are wired into it. This keeps the execution logic stateless between nodes.
The Inner Join is about 20 lines:
const result = [];
for (const leftRow of leftRows) {
const match = rightRows.find(r => r[rightKey] === leftRow[leftKey]);
if (match) result.push({ ...leftRow, ...match });
}
return result;
Standard relational join semantics. Nothing exotic.
What Makes This Different From Just Writing SQL
The usual counterargument here is: "why not just write SQL?" And for people who know SQL and have access to a database, that's a completely valid question.
TAFNE's Node Editor is for the cases where SQL isn't the right tool:
The data doesn't live in a database yet. It's a CSV someone emailed you, plus a live API you want to enrich it with. Writing SQL requires loading both sources into a database first. The Node Editor skips that step entirely.
The pipeline needs to be shared with someone who doesn't know SQL. A visual node graph is readable by anyone who understands the concept of "connect these things in this order." The JSON representation of the graph is portable and human-readable.
The whole thing should run in a browser, with no server, with no account, with data that never leaves the machine. That constraint rules out most SQL-based tools immediately.
Where It Fits
The Node Editor sits in the middle of TAFNE's three-mode architecture. Lab Mode is for cleaning and validating single datasets. The Node Editor is for connecting multiple datasets, sheets, APIs, or both, into a single output. Table Mode is for final layout and export.
Data flows from Lab Mode output into Node Editor, from Node Editor output back into Table Mode. Any sheet produced by any mode is available as a source in any other mode.
That's the loop. And the Node Editor is the part of it that makes multi-source work possible without writing code.
Source: github.com/carnworkstudios/TAFNE