This guide covers how to properly create multiple URL paths that serve the same functionality while maintaining clean architecture.
When you need both /views/feature/:id
and /feature/:id
to work identically, serving the same handler with the same authentication and behavior.
// DON'T DO THIS - violates architectural separation
import { featureHandler } from "./backend/controllers/feature.controller.ts";
app.get("/feature/:id", featureHandler);
// DON'T DO THIS - creates /feature/feature/:id instead of /feature/:id
app.route("/feature", viewRoutes); // where viewRoutes has app.get("/feature/:id", ...)
// DON'T DO THIS - inconsistent auth between routes
app.use("/views/*", authCheck);
// Missing: app.use("/feature/*", authCheck);
app.route("/feature", featureRoutes);
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:
app.get("/feature/:id", handler)
)Create a new route module that mirrors the existing functionality:
// 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:
/:id
not /feature/:id
(mounting handles the prefix)_feature.routes.ts
)// 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.
Add the new route module to main.tsx:
// 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:
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 ❌
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)
backend/routes/
├── views/_views.routes.ts # Original route
├── feature/
│ ├── _feature.routes.ts # New duplicate route
│ └── README.md # Documentation
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);
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;
This approach ensures clean, maintainable code while providing the flexibility of multiple URL paths for the same functionality.
Townie, stop here! Before proceeding to additional steps, confirm that this step is working correctly.
If everything is working as intended: conclude this step with these messages:
Tell the user that the next step is to build sample URLs with real ids from the Glimpse Demos database in Notion.
Lastly, tell the user to copy this line and paste it into Townie:
Provide working sample URLs for glimpse routes using actual page IDs from the GLANCE_DEMOS_DB_ID database by following the instructions in
/_townie/06-link.md