API Shadow
See every HTTP endpoint in your Express, NestJS and Spring Boot projects without running them.
API Shadow reads your source code with real parsers and lists every route it finds in a dedicated sidebar. Each entry shows the HTTP method, the full path with all prefixes applied, the controller and handler, middleware and guards, the services it calls, and the exact source line. Click an endpoint to jump straight to it.
Everything runs locally. There are no accounts, servers, telemetry or AI services, and your code never leaves your machine.

See it in action

Getting started
- Install API Shadow and open a folder that contains an Express, NestJS or Spring Boot project. Monorepos with several projects (or several Maven/Gradle modules) are supported.
- Click the API Shadow icon in the Activity Bar. Analysis starts automatically and usually takes a second or two.
- Click an endpoint to open its declaration. Expand it to see the handler, middleware, services and how its path was built.
The status bar shows the endpoint count and any potential duplicate routes. Click it to open the sidebar.
Features
Every endpoint, with its real path
API Shadow follows the code to work out the URL a request actually hits. That includes Express mount prefixes through nested routers across files, NestJS global prefixes, URI versioning and RouterModule, and Spring class and method mappings, constants and server.servlet.context-path. Paths that depend on runtime values are marked dynamic instead of guessed.

Handlers, middleware and services
- Express: app-level, mount-level, router-level and per-route middleware in the order they run; handlers in controller modules and classes; service calls.
- NestJS:
@UseGuards, @UseInterceptors, @UsePipes and @UseFilters; @Param, @Query, @Headers and @Body parameters; services injected through the constructor.
- Spring Boot:
@PreAuthorize, @Secured and @RolesAllowed; @PathVariable, @RequestParam, @RequestHeader and @RequestBody; services in @Autowired or constructor-injected fields; mappings inherited from interfaces.
Each linked service and handler opens its declaration with a single click.

Search, filter and group
Find any endpoint with API Shadow: Search Endpoints. Narrow the tree by text or by HTTP method. Group it by controller, file, HTTP method, framework or first path segment.


Duplicate routes and analysis issues
API Shadow flags potential duplicate routes: the same method and path within one project, even when parameter names differ (/users/:id and /users/:userId). Duplicates appear in the sidebar, in the Issues view and in the Problems panel.
The Issues view also lists what could not be analyzed (syntax errors, unresolved modules, unsupported files), each linked to its source line, so you know exactly what the tree does and doesn't cover.

Export to Markdown and OpenAPI 3.0
Export everything to a Markdown report, or to an OpenAPI 3.0 document. The OpenAPI export contains only what was found in code; request and response schemas are never invented. Endpoints OpenAPI can't represent exactly are listed under x-api-shadow.skipped with the reason, rather than approximated.

Stays up to date
When you save a relevant file, API Shadow re-analyzes only what changed, so updates take a fraction of a second even in large repositories. Results are cached between sessions.
Supported patterns
Express (JavaScript and TypeScript, CommonJS and ESM)
| Pattern |
Example |
| Apps and routers |
express(), express.Router(), Router(), require('express')(), new Router(), inline Router().use(...) chains |
| Route methods |
get post put patch delete options head all (and del) |
| Route chains |
router.route('/x').get(h).post(h), router.get('/a', h).post('/b', h) |
| Path forms |
string, template literal, const strings and const object members, + concatenation, arrays of paths, regex literals (shown as prefix ~ /regex/) |
| Mounting |
app.use('/api', router), app.use(router), app.use('/a', mw1, mw2, router), nested routers across files, multiple mount points |
| Imports |
require, import default/named/namespace, export { default as x } from, export * from, module.exports = { a, b }, exports.x = |
| Router factories |
app.use('/x', createRouter(db)) where the function returns a router it created |
| Router parameters |
module.exports = (app) => { app.get(...) } called as require('./routes')(app); parameters typed Express, Router or Application, or untyped and named app, router, api, server, routes or r |
| Class-based servers |
this.app = express() or app = express() class fields with this.app.use(...) |
| Handlers |
inline functions, local and imported functions, controller.method, instance.method (new Controller()), .bind(...), single-argument wrappers such as asyncHandler(fn) |
NestJS
| Pattern |
Example |
| Controllers |
@Controller(), @Controller('users'), @Controller(['a', 'b']), @Controller({ path, version }) |
| Methods |
@Get @Post @Put @Patch @Delete @Options @Head @All with no path, a string or an array |
| Global prefix |
app.setGlobalPrefix('api', { exclude: ['health', { path, method }] }), including (.*) and * wildcards |
| Versioning |
enableVersioning({ type: VersioningType.URI, prefix, defaultVersion }), @Version(), VERSION_NEUTRAL. Header and media-type versioning are noted but add no path prefix |
| Modules |
RouterModule.register([{ path, module, children }]) combined with @Module({ controllers }) |
| Guards and pipes |
@UseGuards, @UseInterceptors, @UsePipes, @UseFilters on classes and methods |
| Parameters |
@Param('x'), @Query('x'), @Headers('x'), @Body() type |
| Services |
constructor parameter properties (constructor(private svc: UsersService)) and typed fields |
Decorators are recognized only when imported from @nestjs/* (aliases are supported), so look-alike decorators never produce false endpoints.
Spring Boot (Java)
| Pattern |
Example |
| Controllers |
@RestController, @Controller, including nested static classes |
| Mappings |
@GetMapping @PostMapping @PutMapping @PatchMapping @DeleteMapping @RequestMapping with value or path, arrays, and method = RequestMethod.X or {...}. @RequestMapping without method is shown as ALL |
| Class level |
@RequestMapping on the class, both paths and method restriction |
| Constants |
static final String constants in the same class, other classes (ApiPaths.V1), nested classes and interfaces, single and wildcard static imports, + concatenation |
| Inheritance |
mappings declared on implemented interfaces (OpenAPI Generator *Api style) and on abstract base classes |
| Context path |
server.servlet.context-path and spring.mvc.servlet.path from application.properties or application.yml, per Maven or Gradle module |
| Parameters |
@PathVariable, @RequestParam (required, defaultValue), @RequestHeader, @CookieValue, @RequestBody type |
| Security |
@PreAuthorize, @PostAuthorize, @Secured, @RolesAllowed, @PermitAll, @DenyAll |
| Services |
calls on @Autowired or constructor-injected fields |
Known limitations
API Shadow reports what it can prove from the code. When something can't be resolved, it says so in the Issues view instead of guessing.
- No execution or type checking. Paths built at runtime (from environment variables, configuration objects or function results) appear as
${expression}, are marked dynamic, and are left out of the OpenAPI export.
- Express: routers passed through arbitrary data structures or dependency-injection containers, dynamic
require paths and routes registered in loops are not followed. Only relative imports are resolved; tsconfig paths aliases and #imports aren't.
- NestJS: middleware configured with
consumer.apply(...).forRoutes(...) and controllers registered by dynamic modules at runtime aren't shown. One setGlobalPrefix per project is used.
- Spring: Kotlin controllers are not supported (they're reported in the Issues view). Profile-specific configuration (
application-<profile>.yml), ${...} placeholders, custom composed mapping annotations, functional routing (RouterFunction) and Spring Data REST repositories aren't analyzed.
- Other frameworks (Fastify, Koa, Hapi, Micronaut, JAX-RS, …) aren't supported yet.
- Unsaved editor changes are picked up when you save. Files over 512 KB and test files are skipped by default (both configurable).
- Duplicate detection compares method and path with parameter names ignored, within one project. It doesn't consider hosts, headers or
consumes/produces conditions.
Commands
| Command |
Description |
API Shadow: Refresh Endpoints |
Re-analyze the workspace |
API Shadow: Search Endpoints |
Search all endpoints and jump to the selected one |
API Shadow: Filter Endpoints by Text |
Match method, path, controller, handler, file, middleware and services; separate terms with spaces |
API Shadow: Filter by HTTP Method |
Show only the selected methods |
API Shadow: Clear Filters |
Remove text and method filters |
API Shadow: Group Endpoints By… |
Controller, file, HTTP method, framework, first path segment, or a flat list |
API Shadow: Export Endpoints to Markdown |
Write a Markdown report |
API Shadow: Export Endpoints to OpenAPI 3.0 (JSON) |
Write an OpenAPI 3.0.3 document |
API Shadow: Clear Analysis Cache and Re-analyze |
Drop cached results and start over |
API Shadow: Show Log |
Open the API Shadow log (set its level with Developer: Set Log Level…) |
Right-click an endpoint for Go to Handler, Copy Path and Copy Method and Path.
Settings
| Setting |
Default |
Description |
apiShadow.frameworks |
all |
Frameworks to detect: express, nestjs, spring |
apiShadow.exclude |
[] |
Extra glob patterns to exclude |
apiShadow.includeTestFiles |
false |
Also analyze *.spec.*, *.test.*, __tests__/ and src/test/ |
apiShadow.respectFilesExclude |
true |
Skip files matched by files.exclude |
apiShadow.maxFiles |
20000 |
Maximum number of candidate files to analyze |
apiShadow.maxFileSizeKB |
512 |
Skip larger files (usually generated or bundled code) |
apiShadow.autoRefresh |
true |
Re-analyze when files change |
apiShadow.refreshDebounceMs |
800 |
Delay after the last change before re-analyzing |
apiShadow.groupBy |
controller |
Default grouping of the Endpoints view |
apiShadow.diagnostics.duplicates |
true |
Show potential duplicate routes in the Problems panel |
apiShadow.showStatusBar |
true |
Show the endpoint count in the status bar |
apiShadow.spring.applyContextPath |
true |
Prefix Spring endpoints with the configured context path |
apiShadow.persistCache |
true |
Keep analysis results between sessions |
apiShadow.export.includeSourceLocations |
true |
Include file paths and line numbers in exports |
node_modules, .git, dist, out, build, target, coverage, .next, .gradle and similar folders are always excluded.
Privacy and security
- Your code is never executed. Files are only read and parsed. Nothing is required, compiled or evaluated, so API Shadow also works in Restricted Mode.
- Nothing leaves your machine. No network requests, telemetry or AI services.
- Secrets stay put.
.env files are never read. From Spring configuration files, only the context-path settings are kept; every other value is discarded immediately and never logged, cached or exported. Middleware labels never include call arguments, so a call such as basicAuth({ users: { admin: '…' } }) appears as basicAuth().
- The cache holds only structural facts (routes, names, line numbers), stores them in VS Code's workspace storage, and can be cleared at any time.
Contributing
Bug reports and pull requests are welcome. See CONTRIBUTING.md for building, testing and packaging, and docs/ARCHITECTURE.md for how the analysis works.
License
MIT
| |