Resource Routes
Resource routes allow you to quickly define a set of RESTful routes for a resource. This is particularly useful when building APIs or web applications that follow REST conventions for CRUD operations (Create, Read, Update, Delete).
Basic Resource Routes
To define a complete set of resource routes, use the resource method:
import { Router } from 'bun-router'
const router = new Router()
router.resource('posts', 'PostsController')This single line creates the following routes:
| HTTP Method | URI | Action | Route Name |
|---|---|---|---|
| GET | /posts | index | posts.index |
| GET | /posts/create | create | posts.create |
| POST | /posts | store | posts.store |
| GET | /posts/ | show | posts.show |
| GET | /posts/{id}/edit | edit | posts.edit |
| PUT/PATCH | /posts/ | update | posts.update |
| DELETE | /posts/ | destroy | posts.destroy |
Controller-Based Resources
In the previous example, we passed a controller name as a string: 'PostsController'. This assumes you have a controller class or module with methods corresponding to the resource actions:
// PostsController.ts
export default class PostsController {
// GET /posts
async index(req) {
const posts = await fetchAllPosts()
return Response.json(posts)
}
// GET /posts/create
async create(req) {
return new Response('Create Post Form')
}
// POST /posts
async store(req) {
const data = await req.json()
const post = await createPost(data)
return Response.json(post, { status: 201 })
}
// GET /posts/{id}
async show(req) {
const post = await fetchPost(req.params.id)
return Response.json(post)
}
// GET /posts/{id}/edit
async edit(req) {
const post = await fetchPost(req.params.id)
return new Response(`Edit Post ${req.params.id} Form`)
}
// PUT /posts/{id}
async update(req) {
const data = await req.json()
const post = await updatePost(req.params.id, data)
return Response.json(post)
}
// DELETE /posts/{id}
async destroy(req) {
await deletePost(req.params.id)
return new Response(null, { status: 204 })
}
}Using Callback Functions
Instead of a controller name, you can also pass an object with handler functions:
router.resource('posts', {
index: (req) => {
return Response.json({ posts: [] })
},
show: (req) => {
return Response.json({ id: req.params.id })
},
// Define other action handlers as needed
})Custom Resource Options
You can customize how resource routes are generated:
router.resource('photos', 'PhotosController', {
// Customize parameter name (default is 'id')
parameterName: 'photoId',
// Only generate specific actions
only: ['index', 'show', 'store'],
// Exclude specific actions
except: ['create', 'edit'],
// Add constraints to the parameter
constraints: {
photoId: 'uuid'
},
// Customize names for the routes
names: {
index: 'photos.all',
show: 'photos.view'
},
// Add middleware to all resource routes
middleware: [authMiddleware()]
})Nested Resource Routes
Resources can be nested to express parent-child relationships:
router.resource('posts.comments', 'CommentsController')This generates nested resource routes such as:
| HTTP Method | URI | Action | Route Name |
|---|---|---|---|
| GET | /posts/{postId}/comments | index | posts.comments.index |
| GET | /posts/{postId}/comments/create | create | posts.comments.create |
| POST | /posts/{postId}/comments | store | posts.comments.store |
| GET | /posts/{postId}/comments/ | show | posts.comments.show |
| GET | /posts/{postId}/comments/{id}/edit | edit | posts.comments.edit |
| PUT/PATCH | /posts/{postId}/comments/ | update | posts.comments.update |
| DELETE | /posts/{postId}/comments/ | destroy | posts.comments.destroy |
The parent resource ID is available in req.params.postId and the comment ID in req.params.id.
Shallow Nesting
For nested resources, you might want to avoid deep nesting for certain actions. Use the shallow option for this:
router.resource('posts.comments', 'CommentsController', {
shallow: true
})This generates routes like:
| HTTP Method | URI | Action | Route Name |
|---|---|---|---|
| GET | /posts/{postId}/comments | index | posts.comments.index |
| GET | /posts/{postId}/comments/create | create | posts.comments.create |
| POST | /posts/{postId}/comments | store | posts.comments.store |
| GET | /comments/ | show | posts.comments.show |
| GET | /comments/{id}/edit | edit | posts.comments.edit |
| PUT/PATCH | /comments/ | update | posts.comments.update |
| DELETE | /comments/ | destroy | posts.comments.destroy |
Notice how the show, edit, update, and destroy routes are not nested.
API-Only Resources
If you're building an API and don't need the create/edit routes (which are typically for forms), you can use the apiOnly option:
router.resource('products', 'ProductsController', {
apiOnly: true
})This generates only the following routes:
| HTTP Method | URI | Action | Route Name |
|---|---|---|---|
| GET | /products | index | products.index |
| POST | /products | store | products.store |
| GET | /products/ | show | products.show |
| PUT/PATCH | /products/ | update | products.update |
| DELETE | /products/ | destroy | products.destroy |
Resource Routes in Groups
Resource routes can be defined within route groups:
router.group({
prefix: '/api',
middleware: [apiAuthMiddleware()],
}, () => {
router.resource('users', 'UsersController')
router.resource('posts', 'PostsController')
// Nested resources
router.resource('posts.comments', 'CommentsController')
})Practical Example: Blog API
Here's a complete example of a blog API using resource routes:
import { auth, jsonBody, Router } from 'bun-router'
const router = new Router()
// Apply global middleware
router.use(jsonBody())
// Public routes
router.get('/', () => new Response('Blog API'))
// API routes with authentication
router.group({
prefix: '/api',
middleware: [auth()],
}, () => {
// Users resource
router.resource('users', 'UsersController', {
except: ['create', 'edit'],
middleware: {
index: [adminOnlyMiddleware()],
destroy: [adminOnlyMiddleware()],
}
})
// Posts resource (API only)
router.resource('posts', 'PostsController', {
apiOnly: true,
middleware: {
store: [authorRoleMiddleware()],
update: [ownerOnlyMiddleware()],
destroy: [ownerOnlyMiddleware()],
}
})
// Comments as a nested resource
router.resource('posts.comments', 'CommentsController', {
apiOnly: true,
shallow: true,
middleware: {
destroy: [ownerOrModeratorMiddleware()]
}
})
// Categories (read-only)
router.resource('categories', 'CategoriesController', {
only: ['index', 'show']
})
})
// Start the server
router.serve({ port: 3000 })Custom Resource Action Methods
If you need additional actions that don't fit the standard CRUD pattern, you can define them separately:
// Define the resource
router.resource('posts', 'PostsController')
// Add custom actions
router.post('/posts/{id}/publish', 'PostsController@publish', 'posts.publish')
router.post('/posts/{id}/unpublish', 'PostsController@unpublish', 'posts.unpublish')
router.get('/posts/{id}/history', 'PostsController@history', 'posts.history')Your controller would then include methods for these custom actions:
// In PostsController
async publish(req) {
await publishPost(req.params.id)
return Response.json({ published: true })
}
async unpublish(req) {
await unpublishPost(req.params.id)
return Response.json({ published: false })
}
async history(req) {
const history = await getPostHistory(req.params.id)
return Response.json(history)
}Resource Middleware for Specific Actions
You can apply middleware to specific resource actions:
router.resource('posts', 'PostsController', {
middleware: {
index: [cachingMiddleware()],
store: [validatePostMiddleware()],
update: [validatePostMiddleware(), ownerOnlyMiddleware()],
destroy: [ownerOnlyMiddleware()]
}
})Best Practices
When working with resource routes:
Follow RESTful Conventions: Stick to standard REST patterns for consistency.
Use Appropriate HTTP Methods: GET for retrieval, POST for creation, PUT/PATCH for updates, and DELETE for removal.
Organize Controllers Logically: Keep controller methods organized around resources.
Consider Nesting Carefully: Deeply nested resources can lead to complex URLs; use shallow nesting when appropriate.
Apply Action-Specific Middleware: Add middleware only to the actions that need it to keep your application efficient.
Next Steps
Now that you understand resource routes, check out these related topics:
- Route Parameters - Learn more about parameter handling
- Named Routes - Understand how to use the auto-generated route names
- Middleware - Apply middleware to resource routes