TypeScript endpoint discovery

List All NestJS Routes Across Controllers and Modules

NestJS logs each mapped route at startup and stores controller and handler paths as decorator metadata, but it has no public command that prints an endpoint inventory. @nestjs/swagger generates OpenAPI from the same controllers, yet excluded endpoints, global prefixes, and versioning can make the document differ from what is reachable.

Direct framework method

Start with the routes registered at runtime

Import DiscoveryModule, register a provider like this one, and call app.get(RouteInventory).list() after app.init(). It reads the same controller and handler metadata Nest uses to build its router, without touching the underlying Express or Fastify internals.

NestJS route inventory · typescript
import { Injectable, RequestMethod } from "@nestjs/common";
import { METHOD_METADATA, PATH_METADATA } from "@nestjs/common/constants";
import { DiscoveryService, MetadataScanner, Reflector } from "@nestjs/core";

const joinPath = (...parts: string[]): string =>
  "/" + parts.flatMap((part) => part.split("/")).filter(Boolean).join("/");

@Injectable()
export class RouteInventory {
  constructor(
    private readonly discovery: DiscoveryService,
    private readonly scanner: MetadataScanner,
    private readonly reflector: Reflector,
  ) {}

  list(): string[] {
    return this.discovery.getControllers().flatMap(({ instance, metatype }) => {
      if (!instance || !metatype) return [];
      const prefixes = [this.reflector.get<string | string[]>(PATH_METADATA, metatype) ?? ""].flat();
      const prototype = Object.getPrototypeOf(instance);
      return this.scanner.getAllMethodNames(prototype).flatMap((name) => {
        const handler = prototype[name];
        const paths = this.reflector.get<string | string[] | undefined>(PATH_METADATA, handler);
        if (paths === undefined) return [];
        const method = RequestMethod[this.reflector.get<RequestMethod>(METHOD_METADATA, handler)];
        return prefixes.flatMap((prefix) =>
          [paths].flat().map((path) => `${method} ${joinPath(prefix, path)}`));
      });
    });
  }
}

Where a route-listing snippet stops

  • The whole module graph must resolve its dependencies before discovery can run, which usually needs a working configuration and database connection.
  • Global prefixes, URI versioning, and RouterModule paths are applied outside controller metadata and have to be added to each path separately.
  • Endpoints marked with @ApiExcludeEndpoint or @ApiExcludeController stay reachable but are absent from the generated Swagger document.
  • Decorator metadata does not show guards, DTO validation, or which module imported a controller unless you inspect each one explicitly.

Use source as the second inventory

APIScout analyzes supported NestJS projects from source in VS Code, connects routes with implementation context, and creates an OpenAPI baseline without bootstrapping the application.

Review supported frameworks

Turn the inventory into a reviewable API contract

Export OpenAPI, review the endpoints with the team that owns them, and use the same contract for documentation and security testing.

Open the source-to-OpenAPI workflow