Extending the Router
bun-router allows you to extend the Router class with custom methods, enabling you to create reusable routing patterns and custom functionality tailored to your application's needs.
Basic Extension
Use the extend()
method to add custom methods to your router instance:
import { Router } from 'bun-router'
const router = new Router()
// Extend the router with custom methods
router.extend({
// Custom method for creating admin routes
adminRoute(path, handler) {
return this.get(`/admin${path}`, handler)
.where({ admin: 'true' }) // Add a constraint
},
// Another custom method
apiResource(name, controller) {
// Create a RESTful API resource with specific naming
this.get(`/api/${name}`, `${controller}/getAll`, 'api', `api.${name}.index`)
this.post(`/api/${name}`, `${controller}/create`, 'api', `api.${name}.create`)
this.get(`/api/${name}/{id}`, `${controller}/getOne`, 'api', `api.${name}.show`)
this.put(`/api/${name}/{id}`, `${controller}/update`, 'api', `api.${name}.update`)
this.delete(`/api/${name}/{id}`, `${controller}/delete`, 'api', `api.${name}.delete`)
return this
}
})
// Now use your custom methods
router.adminRoute('/dashboard', showAdminDashboard)
router.apiResource('users', 'UserController')
When to Extend the Router
You should consider extending the router when:
- You have repetitive routing patterns that you use across your application
- You want to create domain-specific methods that match your application's terminology
- You need custom behavior that builds on the core router functionality
- You're building a plugin or shared library that adds functionality to bun-router
Creating Chainable Methods
To make your custom methods chainable (like the built-in router methods), make sure they return this
:
router.extend({
prefix(prefix) {
this._currentPrefix = prefix
return this // Return this for chaining
},
withPrefix(path, handler) {
const fullPath = `${this._currentPrefix || ''}${path}`
return this.get(fullPath, handler) // Already returns this
}
})
// Now you can chain methods
router
.prefix('/dashboard')
.withPrefix('/stats', showStats)
.withPrefix('/users', showUsers)
Practical Extension Examples
Authentication Routes
Create a method for defining authentication-related routes:
router.extend({
auth(options = {}) {
const { loginPath = '/login', registerPath = '/register', logoutPath = '/logout' } = options
this.get(loginPath, 'Auth/ShowLoginForm', 'web', 'auth.login')
this.post(loginPath, 'Auth/Login', 'web')
this.get(registerPath, 'Auth/ShowRegisterForm', 'web', 'auth.register')
this.post(registerPath, 'Auth/Register', 'web')
this.post(logoutPath, 'Auth/Logout', 'web', 'auth.logout')
return this
}
})
// Use the custom auth method
router.auth({ loginPath: '/signin', logoutPath: '/signout' })
Versioned API Routes
Create a method for managing versioned API endpoints:
router.extend({
// Method to handle API versioning
v(version, callback) {
const previousPrefix = this._apiVersionPrefix || ''
this._apiVersionPrefix = `/v${version}`
// Create a group with the version prefix
this.group({
prefix: this._apiVersionPrefix
}, callback)
this._apiVersionPrefix = previousPrefix
return this
},
// Method to create a route with the current API version
apiRoute(method, path, handler, name) {
const versionedPath = `${this._apiVersionPrefix || ''}${path}`
// Use the appropriate HTTP method
if (method === 'GET') return this.get(versionedPath, handler, 'api', name)
if (method === 'POST') return this.post(versionedPath, handler, 'api', name)
if (method === 'PUT') return this.put(versionedPath, handler, 'api', name)
if (method === 'DELETE') return this.delete(versionedPath, handler, 'api', name)
if (method === 'PATCH') return this.patch(versionedPath, handler, 'api', name)
return this
}
})
// Use the versioning methods
router.v(1, () => {
router.apiRoute('GET', '/users', 'API/V1/Users/Index', 'api.v1.users.index')
router.apiRoute('POST', '/users', 'API/V1/Users/Store', 'api.v1.users.store')
})
router.v(2, () => {
router.apiRoute('GET', '/users', 'API/V2/Users/Index', 'api.v2.users.index')
router.apiRoute('POST', '/users', 'API/V2/Users/Store', 'api.v2.users.store')
})
CRUD Operations
Simplify CRUD operations for your entities:
router.extend({
crud(entity, options = {}) {
const {
basePath = `/${entity.toLowerCase()}`,
controller = `${entity}Controller`,
middleware = [],
except = []
} = options
const routes = {
index: { method: 'GET', path: basePath, action: 'index' },
create: { method: 'GET', path: `${basePath}/create`, action: 'create' },
store: { method: 'POST', path: basePath, action: 'store' },
show: { method: 'GET', path: `${basePath}/{id}`, action: 'show' },
edit: { method: 'GET', path: `${basePath}/{id}/edit`, action: 'edit' },
update: { method: 'PUT', path: `${basePath}/{id}`, action: 'update' },
destroy: { method: 'DELETE', path: `${basePath}/{id}`, action: 'destroy' }
}
// Register all routes that aren't in the except array
for (const [routeName, route] of Object.entries(routes)) {
if (except.includes(routeName)) continue
const handler = `${controller}@${route.action}`
if (route.method === 'GET') this.get(route.path, handler, 'web', `${entity.toLowerCase()}.${routeName}`)
if (route.method === 'POST') this.post(route.path, handler, 'web', `${entity.toLowerCase()}.${routeName}`)
if (route.method === 'PUT') this.put(route.path, handler, 'web', `${entity.toLowerCase()}.${routeName}`)
if (route.method === 'DELETE') this.delete(route.path, handler, 'web', `${entity.toLowerCase()}.${routeName}`)
}
return this
}
})
// Use the CRUD generator
router.crud('Post', { except: ['create', 'edit'] }) // Skip create and edit forms for API
router.crud('User', { middleware: [authMiddleware] }) // Add auth middleware
Extension with TypeScript
To get proper TypeScript support for your extended methods, you can use declaration merging:
import { Router } from 'bun-router'
// Extend the Router interface
declare module 'bun-router' {
interface Router {
apiResource(name: string, controller: string): Router;
adminRoute(path: string, handler: ActionHandler): Router;
// Add more custom method signatures here
}
}
const router = new Router()
// Now TypeScript will recognize these methods
router.extend({
apiResource(name, controller) {
// Implementation
return this
},
adminRoute(path, handler) {
// Implementation
return this
}
})
Best Practices
When extending the router, follow these best practices:
- Return the router instance from your methods to support chaining
- Document your custom methods with JSDoc comments
- Use strong typing with TypeScript
- Don't override built-in methods unless you fully understand the implications
- Group related functionality into logical extensions
- Keep your extensions focused on a specific concern
- Maintain predictable naming for your custom methods
Sharing Extensions as Plugins
You can create shareable router extensions as plugins:
// myRouterPlugin.ts
export function installMyRouterPlugin(router) {
return router.extend({
// Custom methods...
specialRoute(path, handler) {
// Implementation
return this
}
})
}
// Using the plugin
import { Router } from 'bun-router'
import { installMyRouterPlugin } from './myRouterPlugin'
const router = new Router()
installMyRouterPlugin(router)
// Now you can use the plugin's methods
router.specialRoute('/example', exampleHandler)
Advanced Example: Multi-tenant Routing
Here's an advanced example implementing multi-tenant routing:
router.extend({
// Set the current tenant
tenant(tenantId) {
this._currentTenant = tenantId
return this
},
// Create a tenant-specific route
tenantRoute(method, path, handler, name) {
if (!this._currentTenant) {
throw new Error('No tenant set. Call tenant() first.')
}
const tenantPath = `/tenants/${this._currentTenant}${path}`
const tenantName = name ? `tenant.${this._currentTenant}.${name}` : undefined
// Use the appropriate HTTP method
if (method === 'GET') return this.get(tenantPath, handler, 'web', tenantName)
if (method === 'POST') return this.post(tenantPath, handler, 'web', tenantName)
if (method === 'PUT') return this.put(tenantPath, handler, 'web', tenantName)
if (method === 'DELETE') return this.delete(tenantPath, handler, 'web', tenantName)
if (method === 'PATCH') return this.patch(tenantPath, handler, 'web', tenantName)
return this
},
// Create tenant-specific routes in a group
tenantGroup(tenantId, callback) {
const previousTenant = this._currentTenant
this._currentTenant = tenantId
// Execute the callback with the tenant set
callback()
// Restore the previous tenant
this._currentTenant = previousTenant
return this
}
})
// Using the multi-tenant routing
router.tenantGroup('acme', () => {
router.tenantRoute('GET', '/dashboard', 'Tenant/Dashboard', 'dashboard')
router.tenantRoute('GET', '/profile', 'Tenant/Profile', 'profile')
})
router.tenantGroup('megacorp', () => {
router.tenantRoute('GET', '/dashboard', 'Tenant/Dashboard', 'dashboard')
router.tenantRoute('GET', '/profile', 'Tenant/Profile', 'profile')
})
Next Steps
Now that you understand how to extend the router, check out related topics: