View Rendering
bun-router provides a convenient way to render views (HTML templates) with the router.view()
method. This feature makes it easy to create web applications that serve dynamic HTML content.
Basic View Rendering
The most basic usage of router.view()
defines a route that renders a template with data:
import { Router } from 'bun-router'
const router = new Router()
// Render a view for the homepage
router.view('/', 'home', {
title: 'Welcome to my site',
message: 'Hello, World!',
})
This will:
- Register a GET route at the path
/
- Render the
home
template (e.g.,resources/views/home.html
) - Pass the data object with
title
andmessage
to the template
View Method Signature
The view
method is flexible and supports multiple parameter combinations:
router.view(
path: string, // The URL path for the route
viewOrData: string | object, // The view name or data (if view name is derived from path)
dataOrOptions: object, // The data to pass to the view or rendering options
optionsOrType: object | 'web' | 'api', // Rendering options or route type
typeOrName: 'web' | 'api' | string, // Route type or route name
name?: string // Route name (if not provided in previous parameter)
): Promise<Router>
View Rendering Options
You can pass rendering options to customize the view output:
router.view('/about', 'about', {
company: 'Acme Corp'
}, {
layout: 'main', // Use main.html as the layout template
status: 200, // HTTP status code (defaults to 200)
headers: { // Additional response headers
'Cache-Control': 'max-age=3600'
}
})
Layouts
Layouts provide a way to wrap content in a common structure (header, footer, navigation). The rendered view will replace the placeholder in the layout:
// Using the layout option
router.view('/dashboard', 'dashboard', userData, {
layout: 'admin' // Uses resources/views/layouts/admin.html
})
You can also set a default layout in your router configuration:
const router = new Router({
views: {
defaultLayout: 'main',
// Other view config options...
}
})
Naming Routes
You can give the view route a name for URL generation:
// Last parameter as the route name
router.view('/contact', 'contact', contactData, {}, 'web', 'contact.page')
// Or directly as the typeOrName parameter
router.view('/contact', 'contact', contactData, {}, 'contact.page')
// Later, generate URL to this route
const contactUrl = router.route('contact.page')
Deriving View Name From Path
If you follow a convention where route paths correspond to view names, you can skip specifying the view name:
// The view name 'products' is derived from the path '/products'
router.view('/products', { products: productsList })
API vs Web Routes
You can specify if a view route is for API or web:
// Default is 'web'
router.view('/profile', 'profile', userData)
// Explicitly mark as web route
router.view('/profile', 'profile', userData, {}, 'web')
// API route with HTML response
router.view('/api/docs', 'api-docs', docsData, {}, 'api')
Template Processing
By default, bun-router
makes use of the bun-plugin-stx
plugin, to allow for...:
wip
- Variable substitution:
{{ variable }}
- Conditionals:
{{#if condition}} content {{else}} alternative {{/if}}
- Loops:
{{#each items}} content with {{ this.property }} {{/each}}
Example template:
<h1>{{ title }}</h1>
{{#if user}}
<p>Welcome back, {{ user.name }}!</p>
{{else}}
<p>Please log in</p>
{{/if}}
<h2>Products:</h2>
<ul>
{{#each products}}
<li>{{ this.name }} - ${{ this.price }}</li>
{{/each}}
</ul>
Custom Template Engines
You can configure a custom template engine in the router options:
const router = new Router({
views: {
viewsPath: 'resources/views',
extensions: ['.html', '.eta'],
customRenderer: (template, data, options) => {
// Your custom rendering logic
return customEngine.render(template, data)
}
}
})
Practical Example
Here's a complete example showing how to use view rendering for a small web application:
import { Router } from 'bun-router'
const router = new Router({
views: {
viewsPath: 'resources/views',
extensions: ['.html'],
cache: true, // Cache templates in production
defaultLayout: 'main',
}
})
// Home page
router.view('/', 'home', {
title: 'Welcome',
featured: await getFeaturedItems()
}, {}, 'home.page')
// About page
router.view('/about', 'about', {
title: 'About Us',
team: teamMembers
}, {}, 'about.page')
// Dynamic product page
router.get('/products/{id}', async (req) => {
const product = await getProductById(req.params.id)
if (!product) {
return new Response('Product not found', { status: 404 })
}
// Render view manually for dynamic routes
const html = await router.renderView('product-detail', {
product,
relatedProducts: await getRelatedProducts(product.id)
}, { layout: 'main' })
return new Response(html, {
headers: { 'Content-Type': 'text/html' }
})
})
// Start the server
router.serve({ port: 3000 })
Best Practices
- Organize View Files: Keep a consistent directory structure for your views
- Use Layouts: Create reusable layouts for consistent page structure
- Partial Templates: Break complex templates into smaller, reusable partials
- Sanitize Data: Ensure data passed to templates is properly sanitized to prevent XSS attacks
- Cache in Production: Enable template caching in production for better performance
Next Steps
Check out these related topics:
- Route Parameters - Handling dynamic parameters in routes
- Named Routes - More on naming routes for URL generation