Static analysis for API routes. Parse route definitions across multiple web frameworks, detect conflicts, shadowed routes, duplicate registrations, parameter name conflicts, and missing methods. Single binary, zero runtime dependencies, CI-friendly.
Every non-trivial web application accumulates route definitions over time. As projects grow, subtle bugs creep in:
- Shadowed routes:
GET /users/:idregistered beforeGET /users/adminmeans/users/adminis unreachable in many frameworks - Duplicate registrations: The same method+path registered twice, often in different files, causing silent overwrites
- Parameter conflicts:
/users/:idand/users/:userIdat the same position with different names - Missing CRUD methods: A resource has GET, POST, PUT but no DELETE — intentional or forgotten?
These issues are caught at runtime (if at all), often in production. routecheck catches them at build time by statically analyzing your source files — no server startup required.
| Framework | Language | Param Style | Auto-Detection |
|---|---|---|---|
| Express | JavaScript/TypeScript | :id |
require('express') / import express |
| Gin | Go | :id, *path |
github.com/gin-gonic/gin |
| FastAPI | Python | {id}, {id:int} |
from fastapi import |
| Chi | Go | {id}, {id:[0-9]+} |
github.com/go-chi/chi |
| Echo | Go | :id, *path |
github.com/labstack/echo |
All parameter styles are normalized to {param} internally for consistent conflict detection.
go install github.com/JSLEEKR/routecheck/cmd/routecheck@latestDownload the latest release from GitHub Releases.
git clone https://github.com/JSLEEKR/routecheck.git
cd routecheck
go build -o routecheck ./cmd/routecheck# Scan current directory recursively
routecheck .
# Scan specific files
routecheck src/routes.js server/main.go
# Scan with glob patterns
routecheck "src/**/*.js"
# Force a specific framework parser
routecheck --framework express src/
# JSON output for CI integration
routecheck --format json src/ > report.json
# List all discovered routes
routecheck --list src/
# Strict mode: treat warnings as errors
routecheck --strict src/routecheck [flags] [paths...]
Flags:
-f, --format string Output format: text, json (default "text")
--framework string Force framework parser: express, gin, fastapi, chi, echo
--ext string Comma-separated file extensions to scan (e.g., .go,.js)
-r, --recursive Recursively scan directories (default true)
-l, --list List all discovered routes
--strict Treat warnings as errors (exit code 1)
-v, --version Show version
Exit Codes:
0 Clean — no errors found
1 Conflicts — errors (or warnings in strict mode) detected
2 Parse error — invalid flags or file access problems
[ERROR] Duplicate route: GET /users registered 2 times
-> GET /users (routes.js:5)
-> GET /users (routes.js:15)
[ERROR] Route GET /users/admin (line 15) is shadowed by GET /users/{id} (line 5)
-> GET /users/{id} (routes.js:5)
-> GET /users/admin (routes.js:15)
[WARN] Potential conflict: GET /users/{id} vs GET /users/{userId}
-> GET /users/{id} (routes.js:5)
-> GET /users/{userId} (api.js:10)
[WARN] Parameter name conflict at position 1: {id} vs {userId}
-> GET /users/{id}/posts (routes.js:5)
-> GET /users/{userId}/posts (api.js:10)
[INFO] Pattern /users has 3 methods but missing DELETE — intentional?
Human-readable output with severity icons and route locations:
routecheck: scanned 12 routes
------------------------------------------------------------
ERRORS (2):
[ERROR] Duplicate route: GET /health registered 2 times
-> GET /health (main.go:8)
-> GET /health (main.go:25)
[ERROR] Route GET /users/admin (line 20) is shadowed by GET /users/{id} (line 10)
-> GET /users/{id} (main.go:10)
-> GET /users/admin (main.go:20)
WARNINGS (1):
[WARN] Potential conflict: GET /users/{id} vs GET /users/{userId}
-> GET /users/{id} (main.go:10)
-> GET /users/{userId} (main.go:22)
------------------------------------------------------------
Summary: 2 errors, 1 warnings, 0 info
Machine-readable output for CI integration:
{
"version": "1.0.0",
"total_routes": 12,
"issues": [
{
"type": "duplicate",
"severity": "error",
"message": "Duplicate route: GET /health registered 2 times",
"routes": [
{
"method": "GET",
"pattern": "/health",
"file": "main.go",
"line": 8,
"framework": "gin"
},
{
"method": "GET",
"pattern": "/health",
"file": "main.go",
"line": 25,
"framework": "gin"
}
]
}
],
"summary": {
"errors": 2,
"warnings": 1,
"info": 0
},
"routes_by_method": {
"GET": 8,
"POST": 2,
"PUT": 1,
"DELETE": 1
},
"routes_by_framework": {
"gin": 12
}
}name: Route Check
on: [push, pull_request]
jobs:
routecheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: '1.26'
- run: go install github.com/JSLEEKR/routecheck/cmd/routecheck@latest
- run: routecheck --strict --format json src/ > routecheck-report.json
- name: Check for issues
run: routecheck --strict src/routecheck:
stage: lint
image: golang:1.26
script:
- go install github.com/JSLEEKR/routecheck/cmd/routecheck@latest
- routecheck --strict .#!/bin/sh
# .git/hooks/pre-commit
routecheck --strict src/
if [ $? -ne 0 ]; then
echo "Route conflicts detected! Fix before committing."
exit 1
firoutecheck auto-detects frameworks by analyzing file content:
| Framework | Detection Signals |
|---|---|
| Express | require('express'), import express, express(), express.Router() |
| Gin | "github.com/gin-gonic/gin", gin.Default(), gin.New() |
| FastAPI | from fastapi, FastAPI(), APIRouter( |
| Chi | "github.com/go-chi/chi", chi.NewRouter() |
| Echo | "github.com/labstack/echo", echo.New() |
File extensions determine which detectors run:
.go— Gin, Chi, Echo.js,.ts,.mjs,.cjs— Express.py— FastAPI
Use --framework to override auto-detection when scanning files without obvious framework indicators.
routecheck understands route grouping and prefix composition:
api := r.Group("/api")
v1 := api.Group("/v1")
v1.GET("/users", listUsers) // detected as /api/v1/usersapi := e.Group("/api")
v1 := api.Group("/v1")
v1.GET("/users", listUsers) // detected as /api/v1/usersrouter = APIRouter(prefix="/items")
app.include_router(router, prefix="/api/v1")
@router.get("/{id}") # detected as /api/v1/items/{id}r.Route("/api/v1", func(r chi.Router) {
r.Get("/users", listUsers) // detected as /api/v1/users
})All framework-specific parameter syntaxes are normalized to {param}:
| Framework | Input | Normalized |
|---|---|---|
| Express | /users/:id |
/users/{id} |
| Gin | /users/:id |
/users/{id} |
| Gin | /files/*filepath |
/files/{filepath} |
| FastAPI | /users/{id:int} |
/users/{id} |
| Chi | /users/{id:[0-9]+} |
/users/{id} |
| Echo | /users/:id |
/users/{id} |
| Echo | /files/*filepath |
/files/{filepath} |
Trailing slashes are stripped (except root /). Leading slashes are ensured.
routecheck can scan projects that use multiple frameworks simultaneously — for example, a Go API server using Gin alongside a Node.js BFF using Express. Routes from all frameworks are combined and analyzed together.
# Scan both Go and JS sources
routecheck server/ frontend/api/This catches cross-framework conflicts like an Express BFF route that shadows a Gin backend route when both serve the same path prefix behind a reverse proxy.
When scanning recursively, these directories are automatically skipped:
- Hidden directories (
.git,.vscode, etc.) node_modulesvendor__pycache__
cmd/routecheck/ CLI entry point
internal/
parser/ Framework-specific route extractors
express.go Express.js parser
gin.go Gin parser
fastapi.go FastAPI parser
chi.go Chi parser
echo.go Echo parser
detect.go Auto-detection logic
types.go Shared types (Route, ParseResult, Parser)
trie/ Route trie data structure
trie.go Insert, walk, path segmentation
analyzer/ Conflict detection engine
analyzer.go Duplicate, conflict, shadow, param, missing checks
framework/ File scanning and route collection
scanner.go Glob, directory walk, dedup, extension filter
output/ Result formatting
output.go Text and JSON formatters
# Run tests
go test ./... -v
# Build
go build -o routecheck ./cmd/routecheck
# Run against test data
./routecheck testdata/express/routes.js
./routecheck testdata/gin/routes.go
./routecheck testdata/fastapi/routes.py
# Run against test data with all frameworks
./routecheck testdata/MIT
- Fork the repository
- Create your feature branch (
git checkout -b feature/my-feature) - Run tests (
go test ./...) - Commit your changes
- Push to the branch
- Open a Pull Request
Built as part of the daily-challenge project by @JSLEEKR.