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/devtools-integration nestjs-spelunker @riaskov/nestjs-graph-visualizer nestjs-doctor dependency-cruiser erdia / typeorm-uml scip-typescript
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 · NestJS-aware · 6 edge kinds · file:line provenance · semantic search

    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 Markdown doc-section nodes with MiniLM-L12 embeddings, exposed via code_search, docs_search, and semantic_search MCP tools. Wedge: cross-cutting message-bus + data-layer coverage at the intersection where no other static tool lives.

    repo →

  • graphify

    LLM-driven · semantic · generic

    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.

    repo →

  • @nestjs/devtools-integration

    official · runtime · authoritative DI

    The canonical NestJS graph tool. Boots your app via NestFactory.create() and snapshots a SerializedGraph of 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; no file:line on emitted nodes; can't run on broken builds.

    npm → · docs →

  • nestjs-spelunker

    runtime · DI only · community

    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.

    repo →

  • @riaskov/nestjs-graph-visualizer

    static AST · DI only · Mermaid / DOT / SVG

    Methodologically closest peer to arch-graph: a CLI that statically walks app.module.ts via AST metadata parsing — no DI container, no NestFactory. 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.

    npm →

  • nestjs-doctor

    static · lint + graph + ER · HTML report

    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.

    npm → · docs →

  • dependency-cruiser

    static · framework-agnostic · import graph

    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 import edges.

    repo →

  • erdia / typeorm-uml

    static · ER diagram · single-domain

    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.

    erdia → · typeorm-uml →

  • scip-typescript

    code intel · symbols/refs/defs · polyglot index

    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 UserService is referenced. Mention as the "if you want IDE-grade refs" answer.

    repo →

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 after NestFactory.create(), so it sees what really got wired up.

  • TypeORM ER diagrams → use erdia or typeorm-uml

    arch-graph emits service → table from @InjectRepository and @ManyToOne / @OneToMany / @ManyToMany / @OneToOne as typed db-relation edges. 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.