FeaturesTemplatesShowcaseTownie
AI
BlogDocsPricing
Log inSign up
lightweight
lightweightglimpse2-runbook
Public
Like
glimpse2-runbook
Home
Code
4
_townie
13
.vtignore
deno.json
main.tsx
Branches
2
Pull requests
Remixes
3
History
Environment variables
Val Town is a collaborative website to build and scale JavaScript apps.
Deploy APIs, crons, & store data – all from the browser, and deployed in milliseconds.
Sign up now
Code
/
_townie
/
05-glimpse.md
Code
/
_townie
/
05-glimpse.md
Search
8/6/2025
Viewing readonly version of main branch: v130
View latest version
05-glimpse.md

Instructions for Adding Duplicate Routes (Multiple Paths to Same Handler)

This guide covers how to properly create multiple URL paths that serve the same functionality while maintaining clean architecture.

Use Case

When you need both /views/feature/:id and /feature/:id to work identically, serving the same handler with the same authentication and behavior.

❌ Common Mistakes to Avoid

1. Direct Controller Import in Main Router

Create val
// DON'T DO THIS - violates architectural separation import { featureHandler } from "./backend/controllers/feature.controller.ts"; app.get("/feature/:id", featureHandler);

2. Mounting Routes at Wrong Path

Create val
// DON'T DO THIS - creates /feature/feature/:id instead of /feature/:id app.route("/feature", viewRoutes); // where viewRoutes has app.get("/feature/:id", ...)

3. Forgetting Authentication Middleware

Create val
// DON'T DO THIS - inconsistent auth between routes app.use("/views/*", authCheck); // Missing: app.use("/feature/*", authCheck); app.route("/feature", featureRoutes);

✅ Correct Implementation Steps

Step 1: Analyze Existing Route Structure

First, understand the current route setup:

# Check existing routes file cat /backend/routes/views/_views.routes.ts # Check main router setup cat /main.tsx

Look for:

  • How the route is currently defined (e.g., app.get("/feature/:id", handler))
  • What authentication middleware is applied
  • How routes are mounted in main.tsx

Step 2: Create Dedicated Route Module

Create a new route module that mirrors the existing functionality:

Create val
// Create /backend/routes/feature/_feature.routes.ts import { Hono } from "npm:hono@3.12.12"; import { featureHandler } from "../../controllers/feature.controller.ts"; const app = new Hono(); // Use /:id (not /feature/:id) since it will be mounted at /feature app.get("/:id", featureHandler); export default app;

Key Points:

  • Route path is /:id not /feature/:id (mounting handles the prefix)
  • Import the same controller used by the original route
  • Follow the same naming convention (_feature.routes.ts)

Step 3: Add Route Documentation

Create val
// Create /backend/routes/feature/README.md # Feature Routes This directory contains routes for the feature functionality. ## Routes ### GET /:id - **Purpose**: [Copy purpose from original route docs] - **Authentication**: Required (Google OAuth) - **Parameters**: `id` - [Parameter description] - **Response**: [Response description] This is the same functionality as `/views/feature/:id` but mounted at `/feature/:id` for convenience.

Step 4: Update Main Router

Add the new route module to main.tsx:

Create val
// Add import import featureRoutes from "./backend/routes/feature/_feature.routes.ts"; // Add authentication middleware (same as original) app.use("/feature/*", authCheck); // Mount routes app.route("/feature", featureRoutes);

Critical Order:

  1. Import route modules (not controllers)
  2. Apply authentication middleware
  3. Mount routes after middleware

Step 5: Verify Route Mounting Logic

Ensure you understand the path composition:

Main router mount: app.route("/feature", featureRoutes)
Route definition: app.get("/:id", handler)
Final URL: /feature/:id ✅

NOT:
Route definition: app.get("/feature/:id", handler)
Final URL: /feature/feature/:id ❌

Step 6: Test Both Routes

Test that both routes work identically:

# Test original route curl /views/feature/test-id # Test new route curl /feature/test-id # Both should return identical responses (login page if not authenticated)

Complete Example Implementation

File Structure

backend/routes/
├── views/_views.routes.ts          # Original route
├── feature/
│   ├── _feature.routes.ts          # New duplicate route
│   └── README.md                   # Documentation

main.tsx

Create val
import { Hono } from "npm:hono@3.12.12"; import { lastlogin } from "https://esm.town/v/stevekrouse/lastlogin_safe"; // Import route modules (NOT controllers) import viewRoutes from "./backend/routes/views/_views.routes.ts"; import featureRoutes from "./backend/routes/feature/_feature.routes.ts"; import authCheck from "./backend/routes/authCheck.ts"; const app = new Hono(); // Apply authentication to both routes app.use("/views/*", authCheck); app.use("/feature/*", authCheck); // Mount routes app.route("/views", viewRoutes); app.route("/feature", featureRoutes); export default lastlogin(app.fetch);

_feature.routes.ts

Create val
import { Hono } from "npm:hono@3.12.12"; import { featureHandler } from "../../controllers/feature.controller.ts"; const app = new Hono(); app.get("/:id", featureHandler); export default app;

Architecture Principles

✅ Maintain Clean Separation

  • Main router → Route modules → Controllers → Services
  • Never import controllers directly in main router
  • Each route module handles one logical grouping

✅ Consistent Authentication

  • Apply same middleware to both route paths
  • Ensure identical security behavior
  • Document authentication requirements

✅ DRY Principle

  • Reuse existing controllers and services
  • Don't duplicate business logic
  • Share the same handler implementation

Testing Checklist

  • Both routes return identical responses
  • Authentication works the same on both routes
  • No 404 errors on either route
  • Route parameters are correctly extracted
  • Error handling is consistent
  • Documentation is updated

Troubleshooting

Route Returns 404

  • Check route mounting path vs route definition path
  • Verify authentication middleware is applied before route mounting
  • Ensure route module exports default app

Different Behavior Between Routes

  • Verify both routes use the same controller
  • Check that authentication middleware is applied to both
  • Confirm route parameters are extracted identically

Architecture Violations

  • Never import controllers in main router
  • Always go through route modules
  • Maintain consistent file naming conventions

This approach ensures clean, maintainable code while providing the flexibility of multiple URL paths for the same functionality.

Go to top
X (Twitter)
Discord community
GitHub discussions
YouTube channel
Bluesky
Product
FeaturesPricing
Developers
DocsStatusAPI ExamplesNPM Package Examples
Explore
ShowcaseTemplatesNewest ValsTrending ValsNewsletter
Company
AboutBlogCareersBrandhi@val.town
Terms of usePrivacy policyAbuse contact
© 2025 Val Town, Inc.