Skip to content

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:

typescript
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:

  1. Register a GET route at the path /
  2. Render the home template (e.g., resources/views/home.html)
  3. Pass the data object with title and message to the template

View Method Signature

The view method is flexible and supports multiple parameter combinations:

typescript
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:

typescript
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:

typescript
// 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:

typescript
const router = new Router({
  views: {
    defaultLayout: 'main',
    // Other view config options...
  }
})

Naming Routes

You can give the view route a name for URL generation:

typescript
// 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:

typescript
// 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:

typescript
// 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: &#123;&#123; variable &#125;&#125;
  • Conditionals: &#123;&#123;#if condition&#125;&#125; content &#123;&#123;else&#125;&#125; alternative &#123;&#123;/if&#125;&#125;
  • Loops: &#123;&#123;#each items&#125;&#125; content with &#123;&#123; this.property &#125;&#125; &#123;&#123;/each&#125;&#125;

Example template:

html
<h1>&#123;&#123; title &#125;&#125;</h1>

&#123;&#123;#if user&#125;&#125;
  <p>Welcome back, &#123;&#123; user.name &#125;&#125;!</p>
&#123;&#123;else&#125;&#125;
  <p>Please log in</p>
&#123;&#123;/if&#125;&#125;

<h2>Products:</h2>
<ul>
  &#123;&#123;#each products&#125;&#125;
    <li>&#123;&#123; this.name &#125;&#125; - $&#123;&#123; this.price &#125;&#125;</li>
  &#123;&#123;/each&#125;&#125;
</ul>

Custom Template Engines

You can configure a custom template engine in the router options:

typescript
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:

typescript
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

  1. Organize View Files: Keep a consistent directory structure for your views
  2. Use Layouts: Create reusable layouts for consistent page structure
  3. Partial Templates: Break complex templates into smaller, reusable partials
  4. Sanitize Data: Ensure data passed to templates is properly sanitized to prevent XSS attacks
  5. Cache in Production: Enable template caching in production for better performance

Next Steps

Check out these related topics:

Released under the MIT License.