Compare
How arch-graph compares.
A capability matrix across nine adjacent tools — the ones a NestJS architect might reasonably reach for when they want to understand a monorepo. This is a vibe-based coverage check (does a tool emit this edge kind, yes/no/partially?), not a head-to-head benchmark. Numbers live on the main page; this page is the orthogonal "what can each tool see?" question.
Capability claims here are derived from public docs and README pages of each tool — not from running them locally. The full research notes live in COMPETITIVE-LANDSCAPE.md in the repo (currently a local research artifact). Spotted an inaccuracy? Open an issue.
Capability matrix
What each tool covers.
Rows are capability axes. Columns are tools. = first-class support, = partial / via inference / scope-narrowed, = not covered by construction. Hover a cell for context where applicable.
| Capability | arch-graph | graphify | @nestjs/ |
nestjs- |
@riaskov/ |
nestjs- |
dependency- |
erdia / typeorm-uml | scip- |
|---|---|---|---|---|---|---|---|---|---|
| NATS pub/sub edges | ✓ | ◐ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ |
| BullMQ producer/consumer | ✓ | ◐ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ |
TypeORM @InjectRepository → table |
✓ | ◐ | ✗ | ✗ | ✗ | ◐ | ✗ | ✗ | ✗ |
TypeORM entity-relation (ER) → db-relation |
✓ | ◐ | ✗ | ✗ | ✗ | ✓ | ✗ | ✓ | ✗ |
| HTTP inter-service calls | ✓ | ◐ | ◐ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ |
| NestJS DI modules / providers / exports | ✓ | ◐ | ✓ | ✓ | ✓ | ✓ | ✗ | ✗ | ✗ |
| Controllers + entrypoints | ✓ | ◐ | ✓ | ✓ | ✓ | ✓ | ✗ | ✗ | ✗ |
Guards / Interceptors / Pipes (di-guard / di-interceptor / di-pipe) |
✓ | ✗ | ✓ | ◐ | ◐ | ◐ | ✗ | ✗ | ✗ |
| Conditional DI at runtime | ✗ | ✗ | ✓ | ✓ | ✗ | ✗ | ✗ | ✗ | ✗ |
TS imports — static + dynamic + CommonJS require |
✓ | ◐ | ✗ | ✗ | ✗ | ✗ | ✓ best | ✗ | ◐ |
| Service → lib aggregation | ✓ | ◐ | ✗ | ✗ | ✗ | ✗ | ◐ | ✗ | ✗ |
Import cycle detection (diagnostics.cycles) |
✓ | ✗ | ✗ | ✗ | ✗ | ◐ | ✓ | ✗ | ✗ |
| Typed JSON graph | ✓ | ✓ | ✓ | ✓ | ✗ | ✗ | ✓ | ✗ | ◐ |
file:line on every edge |
✓ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ◐ |
| Diagnostics for unresolved sites | ✓ | ✗ | ✗ | ✗ | ✗ | ◐ | ◐ | ✗ | ✗ |
| Static (no boot required) | ✓ | ◐ | ✗ | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Deterministic (no LLM) | ✓ | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Works on broken builds | ✓ | ◐ | ✗ | ✗ | ✓ | ✓ | ✓ | ◐ | ✗ |
| CLI query subcommands | ✓ | ◐ | ✗ | ◐ | ◐ | ◐ | ✓ | ◐ | ◐ |
| MCP server | ✓ 15 tools | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ |
| Semantic search (opt-in) | ✓ 3 tools | ✓ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ◐ |
| Markdown docs indexed | ✓ | ✓ | ✗ | ✗ | ✗ | ◐ | ✗ | ✗ | ✗ |
| Claude Code skill | ✓ | ✓ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ |
| Pre-commit hook | ✓ | ✗ | ✗ | ✗ | ✗ | ✗ | ◐ | ✗ | ✗ |
The capability column for arch-graph reflects what ships today. Cells marked with the tooltip "planned" track items in the ROADMAP (cycle detection, ER edges, Guards/Interceptors/Pipes).
Measured
How they fared on the same 40 questions.
We re-ran the 10 auto-generated questions per project (40 total) against each tool. Same compression rules, same cl100k_base encoder, same questions. Recall = substring-presence of arch-graph's ground-truth labels in the tool's compact context. Different tools answer different questions — read the caveats.
| Tool | Mode | Avg tokens | Mean recall | Coverage profile | Caveat |
|---|---|---|---|---|---|
| arch-graph | static | 39,779 | 100% | NATS · BullMQ · TypeORM (+ ER) · DI (+ Guards/Interceptors/Pipes) · HTTP · imports (static + dynamic + CJS) · cycle detection | baseline — questions auto-derived from arch-graph's own nodes, so it scores 100% by construction |
| graphify | LLM-driven | 569,924 | 39% | generic semantic concepts | not designed for NestJS message-bus topology — strength is cross-cutting / "what's this codebase about". The recall number jumped vs. the previous run because the question seed changed (auto-generated questions are re-derived from arch-graph's current node set, which expanded this cycle) |
| @nestjs/devtools-integration | runtime (official) | 65,395 | 9.2% | NestJS DI of one running app | scored 87.5% on module-imports (its native domain), 0% on NATS / BullMQ / TypeORM / cross-service. We stubbed DB / NATS / JWT / Redis env vars to make bootstrap succeed — in CI without secrets, 3 of 4 projects would fail at boot. |
| nestjs-spelunker | runtime | 13,535 | 10.6% | NestJS module-import graph | cheapest in tokens (it captures only one architectural layer) — 100% on module-imports, 0% on cross-cutting questions. Bootstrap required a harness bypassing onModuleInit hooks (real onModuleInit hit live external services). |
| @riaskov/nst-graph-visualizer | runtime (despite "static" docs) | ~125 (partial) | 1.3% | NestJS module decorators | documented as static AST, but in practice require()s the root module. On 3 of 4 projects it couldn't load the main app at all — bidirectional TypeORM forwardRef relations created CJS require cycles in entity barrel files (a common NestJS+TypeORM pattern). |
| dependency-cruiser | static, generic | ~660,344† | 40.8%† | file-level imports (best in class) | †The recall number is misleading. By default depcruise apps libs walks only .js/.mjs — TypeScript is not parsed on a typical pnpm monorepo. The 40.8% comes from substring matches like broadcast against caniuse-lite/broadcastchannel.js, not from real architectural facts. 0% on every NATS / BullMQ / TypeORM / module-import question. Per-category numbers are the honest signal. |
Reading the heuristic fairly
Our scoring (substring-presence of ground-truth labels) is a permissive necessary-condition heuristic: "did the tool's output even contain the answer". It's not an end-to-end LLM eval. The dependency-cruiser row demonstrates how the heuristic can mislead — a tool that extracts no NestJS-architecture facts can still score double-digit recall purely from file-path coincidences (be-project-b appears in apps/be-project-b/webpack.config.js). The per-category breakdown (NATS / BullMQ / TypeORM / DI / imports) is the more honest cut. arch-graph stays at 100% across every category by construction; every other tool's strength shows up as a single category spike.
What this benchmark is and isn't
- It is: a fair side-by-side on the questions arch-graph is built to answer (NestJS architectural relationships, cited to file:line).
- It isn't: a verdict on which tool is "best". devtools answers the official-DI question; spelunker answers it cheapest; graphify handles cross-cutting concepts; dep-cruiser is rock-solid on plain TS file imports. arch-graph is built for the intersection they don't cover.
- Methodology gap: none of these are runtime-trace tools (OpenTelemetry service maps etc.). Those find edges static analysis can't — different category, complementary not competing.
Each tool was run on 4 reference monorepos (the same projects used in the main bench) with auto-generated questions from arch-graph's compare command. Per-project raw outputs + scoring details: see bench/competitive-bench.md in the repo.
Per-tool analysis
What each tool actually does.
Short framing for each. Read alongside the matrix above — the table tells you what, these cards tell you why.
-
arch-graph
Static ts-morph extractor producing a typed JSON graph across NATS pub/sub, BullMQ queues, TypeORM repositories, NestJS DI, inter-service HTTP, and TS imports. Every edge cites
file:line. Ships CLI, MCP server (15 tools: 12 structural + 3 semantic), and Claude Code skill. Optional semantic layer indexes code nodes and Markdowndoc-sectionnodes with MiniLM-L12 embeddings, exposed viacode_search,docs_search, andsemantic_searchMCP tools. Wedge: cross-cutting message-bus + data-layer coverage at the intersection where no other static tool lives. -
graphify
LLM-driven semantic graph builder over any codebase. Produces a JSON graph but isn't specialized for NestJS message-bus patterns — infers structure rather than parsing decorators, so NATS/BullMQ/TypeORM edges either get inferred imprecisely or collapse to generic semantic relations. Useful where the language/framework is unknown; weak on call-graph specifics.
-
@nestjs/devtools-integration
The canonical NestJS graph tool. Boots your app via
NestFactory.create()and snapshots aSerializedGraphof modules, providers, controllers, guards/interceptors/pipes, and HTTP/WS/gRPC/GraphQL entrypoints. On the narrow "module DI graph" question this is more authoritative than any static tool — it's literally Nest's own metadata. Doesn't extract NATS subjects, BullMQ queue names, or TypeORM table edges as typed edges; nofile:lineon emitted nodes; can't run on broken builds. -
nestjs-spelunker
The popular community DI grapher. Runtime-based (needs
NestFactory.create). Covers modules / providers / controllers / exports / injection tokens, emits JSON arrays + Mermaid-mappable edges. Useful head-to-head on the DI subset specifically. No cross-cutting NATS / BullMQ / TypeORM coverage. -
@riaskov/nestjs-graph-visualizer
Methodologically closest peer to arch-graph: a CLI that statically walks
app.module.tsvia AST metadata parsing — no DI container, noNestFactory. Narrower scope: NestJS module DI only, no NATS/BullMQ/TypeORM. Output is Mermaid / DOT / SVG, not JSON. The fairest "no boot required" baseline for the DI subset. -
nestjs-doctor
Static AST scanner that runs ~50 rules and produces a health score, plus a module graph and TypeORM ER diagram in a self-contained HTML report. Comparable on DI + ER coverage; not comparable as a graph data producer (no first-class JSON export). Different deliverable: lint-oriented health check, not an extractor for downstream agents.
-
dependency-cruiser
Production-grade dependency analyzer for JS/TS/CoffeeScript across ES6/CJS/AMD. Validates configurable rules and emits JSON / dot / mermaid / csv / html. The honest baseline for file-level imports — battle-tested across module systems, more than arch-graph is. Framework-agnostic by design: NestJS decorators, NATS subjects, BullMQ queues, and TypeORM entities all collapse to ordinary
importedges. -
erdia / typeorm-uml
Live ER-diagram generators that read TypeORM entities and emit PlantUML / Mermaid / SVG / HTML. Cover the ER sub-graph only — no service-to-service edges, no message bus, no DI. Peer for a TypeORM-only sub-bench; not a peer for the "architecture Q&A" workload.
-
scip-typescript
Sourcegraph's TypeScript indexer. Uses the official typechecker to emit SCIP protobuf for defs/refs/symbol navigation — compiler-accurate, cross-repo. Different abstraction: code intel (symbols), not architectural edges. Won't tell you who publishes a NATS subject, but will tell you everywhere
UserServiceis referenced. Mention as the "if you want IDE-grade refs" answer.
Honest losses
Where arch-graph is the wrong tool.
If your problem maps to one of these, reach for the tool that's actually built for it. arch-graph is not trying to be every tool — it's trying to be the one that lives at the intersection no other static tool covers.
-
Conditional DI at runtime → use @nestjs/devtools-integration
Static decorator parsing can't resolve "this provider is conditionally registered when
NODE_ENV === 'prod'." DevTools snapshots Nest's actual runtime metadata afterNestFactory.create(), so it sees what really got wired up. -
TypeORM ER diagrams → use erdia or typeorm-uml
arch-graph emits
service → tablefrom@InjectRepositoryand@ManyToOne/@OneToMany/@ManyToMany/@OneToOneas typeddb-relationedges. What it does not do is render those as a visual ER diagram. erdia and typeorm-uml produce Mermaid / PlantUML / SVG ER diagrams directly — use them when you need a schema diagram rather than graph edges for agent queries. -
Pure file-imports across module systems → use dependency-cruiser
dep-cruiser handles ESM / CJS / AMD with battle-tested resolvers, has years of rule tuning, and ships a richer rule DSL. On the narrow "which file imports which file?" question, arch-graph should be roughly tied — or strictly worse in legacy CJS corner cases. dep-cruiser is the credible baseline there.
-
Cross-repo / cross-language → use scip-typescript or Joern
arch-graph is a single-monorepo, TypeScript-only static extractor by charter. If you need code intel across repos (Sourcegraph SCIP) or a polyglot CPG (Joern), arch-graph isn't the right abstraction level. Joern emits AST/CFG/DDG; arch-graph emits architectural edges. Different products.
-
Runtime topology from prod traffic → use OpenTelemetry service maps
otelmap / spanmetrics / vendor APMs (Tempo, Elastic) build service-to-service topology from live spans. They find edges arch-graph can't see (dynamically constructed subjects, prod-only paths) and miss edges that exist in code but never fire. Complementary, not competing — and outside arch-graph's static charter.
Methodology
How the matrix was filled in.
Cells were assigned by reading each tool's public README / npm page / official docs, and asking: does this tool emit this kind of edge as a first-class typed artifact? No installs, no runs — this is a coverage check, not a benchmark. The corresponding head-to-head numbers (recall, token cost) are on the main page; that's the orthogonal "how well does it actually answer questions?" question.
The numeric head-to-head against graphify lives at bench/competitive-bench.md (40-question structural bench, preserved as historical reference) and docs/comparisons/2026-05-17-arch-graph-vs-graphify-eval.md (103-question post-semantic bench). Extending the numeric benchmark to @nestjs/devtools-integration, @riaskov/nestjs-graph-visualizer, and dependency-cruiser is on the ROADMAP. Until that lands, this matrix is the honest one-page answer to "what does each tool see?".
Spotted a wrong cell? Open an issue with a link to the upstream doc that contradicts the assignment — corrections welcome.