Created
June 25, 2025 12:38
-
-
Save JonFranchi/910252d44fe8cc9689443e97c855581f to your computer and use it in GitHub Desktop.
Koa Dynamic Routing Options
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const Koa = require('koa'); | |
const Router = require('@koa/router'); | |
const app = new Koa(); | |
const router = new Router(); | |
console.log('🚀 Koa Dynamic Routing Demo\n'); | |
// ============================================================================= | |
// SOLUTION 1: Static Route (your starting point) | |
// ============================================================================= | |
router.get('/api/v1/thing/stuff', (ctx) => { | |
ctx.body = { | |
solution: 1, | |
type: 'static', | |
message: 'Static route: /api/v1/thing/stuff', | |
matched: 'thing/stuff' | |
}; | |
}); | |
// ============================================================================= | |
// SOLUTION 2: Basic Dynamic Parameters (thing and stuff are dynamic) | |
// ============================================================================= | |
router.get('/api/v1/:category/:item', (ctx) => { | |
const { category, item } = ctx.params; | |
ctx.body = { | |
solution: 2, | |
type: 'basic-dynamic', | |
message: `Dynamic route with 2 parameters`, | |
params: { category, item }, | |
path: `${category}/${item}` | |
}; | |
}); | |
// ============================================================================= | |
// SOLUTION 3: Custom Middleware for Unlimited Segments | |
// ============================================================================= | |
const multiSegmentMatcher = (ctx, next) => { | |
const path = ctx.path; | |
const apiV2Match = path.match(/^\/api\/v2\/(.+)$/); | |
if (apiV2Match) { | |
const segments = apiV2Match[1].split('/').filter(s => s.length > 0); | |
ctx.multiSegment = { | |
solution: 3, | |
type: 'unlimited-segments', | |
message: 'Custom middleware handling unlimited segments', | |
segments, | |
segmentCount: segments.length, | |
interpretation: { | |
category: segments[0] || null, | |
item: segments[1] || null, | |
additional: segments.slice(2) | |
} | |
}; | |
} | |
return next(); | |
}; | |
router.get(/^\/api\/v2\/(.+)$/, multiSegmentMatcher, (ctx) => { | |
ctx.body = ctx.multiSegment || { error: 'No match found' }; | |
}); | |
// ============================================================================= | |
// SOLUTION 4: Pure Regex Route Matching | |
// ============================================================================= | |
router.get(/^\/api\/v3\/([^\/]+)\/([^\/]+)(?:\/(.*))?$/, (ctx) => { | |
const category = ctx.params[0]; | |
const item = ctx.params[1]; | |
const additional = ctx.params[2]; | |
ctx.body = { | |
solution: 4, | |
type: 'regex-matching', | |
message: 'Pure regex route with optional additional segments', | |
params: { category, item }, | |
additional: additional ? additional.split('/').filter(s => s.length > 0) : [], | |
fullPath: `${category}/${item}${additional ? '/' + additional : ''}` | |
}; | |
}); | |
// ============================================================================= | |
// SOLUTION 5: Layered Approach (multiple routes, most specific first) | |
// ============================================================================= | |
router.get('/api/v4/:category/:item/:extra1/:extra2', (ctx) => { | |
const { category, item, extra1, extra2 } = ctx.params; | |
ctx.body = { | |
solution: 5, | |
type: 'layered-4-segments', | |
message: 'Matched 4-segment pattern', | |
segments: [category, item, extra1, extra2] | |
}; | |
}); | |
router.get('/api/v4/:category/:item/:extra1', (ctx) => { | |
const { category, item, extra1 } = ctx.params; | |
ctx.body = { | |
solution: 5, | |
type: 'layered-3-segments', | |
message: 'Matched 3-segment pattern', | |
segments: [category, item, extra1] | |
}; | |
}); | |
router.get('/api/v4/:category/:item', (ctx) => { | |
const { category, item } = ctx.params; | |
ctx.body = { | |
solution: 5, | |
type: 'layered-2-segments', | |
message: 'Matched 2-segment pattern (fallback)', | |
segments: [category, item] | |
}; | |
}); | |
// ============================================================================= | |
// SOLUTION 6: Simple Catch-All Test | |
// ============================================================================= | |
router.get('/api/v5/:param1/(.*)', (ctx) => { | |
const param1 = ctx.params.param1; | |
const param2 = ctx.params[0]; // The catch-all captures everything after param1 | |
console.log(ctx.params); | |
ctx.body = { | |
solution: 6, | |
type: 'simple-catch-all', | |
message: 'Simple catch-all with standard Koa routing', | |
param1, | |
param2, | |
params: ctx.params | |
}; | |
}); | |
// ============================================================================= | |
// Demo Routes & Documentation | |
// ============================================================================= | |
router.get('/', (ctx) => { | |
ctx.body = { | |
title: 'Koa Dynamic Routing Demo - Multiple Solutions', | |
solutions: [ | |
{ | |
id: 1, | |
name: 'Static Route', | |
example: '/api/v1/thing/stuff', | |
description: 'Your original fixed route' | |
}, | |
{ | |
id: 2, | |
name: 'Basic Dynamic', | |
example: '/api/v1/books/fiction', | |
description: 'Making thing/stuff dynamic' | |
}, | |
{ | |
id: 3, | |
name: 'Unlimited Segments (Middleware)', | |
example: '/api/v2/books/fiction/mystery/agatha-christie', | |
description: 'Custom middleware for any number of segments' | |
}, | |
{ | |
id: 4, | |
name: 'Regex Matching', | |
example: '/api/v3/books/fiction/mystery/author', | |
description: 'Pure regex with optional additional segments' | |
}, | |
{ | |
id: 5, | |
name: 'Layered Routes', | |
examples: [ | |
'/api/v4/books/fiction', | |
'/api/v4/books/fiction/mystery', | |
'/api/v4/books/fiction/mystery/author' | |
], | |
description: 'Multiple routes, most specific matches first' | |
} | |
] | |
}; | |
}); | |
app.use(router.routes()); | |
app.use(router.allowedMethods()); | |
const PORT = 3000; | |
app.listen(PORT, () => { | |
console.log(`✅ Server running on http://localhost:${PORT}`); | |
console.log('\n📋 Test these URLs:'); | |
console.log('• http://localhost:3000/ (documentation)'); | |
console.log('• http://localhost:3000/api/v1/thing/stuff (static original)'); | |
console.log('• http://localhost:3000/api/v1/books/fiction (basic dynamic)'); | |
console.log('• http://localhost:3000/api/v2/books/fiction/mystery/author (unlimited)'); | |
console.log('• http://localhost:3000/api/v3/books/fiction/mystery/author (regex)'); | |
console.log('• http://localhost:3000/api/v4/books/fiction (layered 2-seg)'); | |
console.log('• http://localhost:3000/api/v4/books/fiction/mystery (layered 3-seg)'); | |
console.log('• http://localhost:3000/api/v4/books/fiction/mystery/author (layered 4-seg)'); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment