Skip to content

Elysia Adapter

binja provides a first-class adapter for Elysia, the ergonomic web framework for Bun.

Installation

Terminal window
bun add binja elysia

Basic Usage

import { Elysia } from 'elysia'
import { binja } from 'binja/elysia'
const app = new Elysia()
// Add binja plugin
.use(binja({
root: './views',
}))
// Render templates with render()
.get('/', ({ render }) => render('index', { title: 'Home' }))
.get('/users/:id', async ({ render, params }) => {
const user = await getUser(params.id)
return render('users/profile', { user })
})
.listen(3000)
console.log('Server running at http://localhost:3000')

Configuration Options

app.use(binja({
root: './views', // Template directory
extension: '.html', // Default extension
engine: 'jinja2', // jinja2 | handlebars | liquid | twig
cache: true, // Cache compiled templates
globals: { // Global context
siteName: 'My App',
year: new Date().getFullYear(),
},
layout: 'layouts/base', // Optional layout template
contentVar: 'content', // Content variable name in layout
}))

Options Reference

OptionTypeDefaultDescription
rootstring./viewsTemplate directory
extensionstring.htmlDefault file extension
enginestringjinja2Template engine
cachebooleantrue (prod)Cache compiled templates
debugbooleanfalseShow error details
globalsobject{}Global context variables
layoutstring-Layout template path
contentVarstringcontentContent variable name

Using Layouts

const app = new Elysia()
.use(binja({
root: './views',
layout: 'layouts/base',
}))
.get('/', ({ render }) => render('pages/home', { title: 'Home' }))

layouts/base.html

<!DOCTYPE html>
<html>
<head>
<title>{{ title }} | {{ siteName }}</title>
</head>
<body>
{{ content|safe }}
</body>
</html>

Multi-Engine Support

const app = new Elysia()
.use(binja({
root: './views',
engine: 'handlebars', // Use Handlebars syntax
}))
.get('/', ({ render }) => render('index', { name: 'World' }))

views/index.html (Handlebars syntax)

<h1>Hello {{name}}!</h1>
{{#if items}}
{{#each items}}
<li>{{this}}</li>
{{/each}}
{{/if}}

Cache Management

import { clearCache, getCacheStats } from 'binja/elysia'
const app = new Elysia()
.use(binja({ root: './views' }))
.get('/admin/clear-cache', () => {
clearCache()
return { success: true }
})
.get('/admin/cache-stats', () => {
return getCacheStats()
})

With HTMX

const app = new Elysia()
.use(binja({ root: './views' }))
// Full page
.get('/', ({ render }) => render('index', { items: [] }))
// HTMX partial - just the component
.post('/items', async ({ render, body }) => {
const item = await createItem(body)
return render('components/item', { item })
})
.delete('/items/:id', async ({ params }) => {
await deleteItem(params.id)
return '' // HTMX removes the element
})

Type Safety

Elysia’s type system works seamlessly with binja:

const app = new Elysia()
.use(binja({ root: './views' }))
.get('/', ({ render }) => {
// TypeScript knows render() returns Response
return render('index', {
title: 'Home',
count: 42, // Type checked
})
})

Error Handling

const app = new Elysia()
.use(binja({
root: './views',
debug: process.env.NODE_ENV !== 'production',
}))
.onError(({ render, code, error }) => {
if (code === 'NOT_FOUND') {
return render('errors/404')
}
return render('errors/500', {
error: process.env.NODE_ENV !== 'production' ? error.message : 'Error'
})
})

Plugin Pattern

Create a reusable plugin with binja:

import { Elysia } from 'elysia'
import { binja } from 'binja/elysia'
// Reusable template plugin
export const templatePlugin = new Elysia({ name: 'templates' })
.use(binja({
root: './views',
cache: process.env.NODE_ENV === 'production',
globals: {
siteName: 'My App',
year: new Date().getFullYear(),
},
}))
// Use in your app
const app = new Elysia()
.use(templatePlugin)
.get('/', ({ render }) => render('index'))