Views & Templates
res.render(), .bxm templates, and sharing layout across pages.
Setting up a views directory
bxsapp.set( "views", expandPath( "./views" ) )
Every view path passed to res.render() is resolved against this directory, with a containment check — a view path can't escape it, even if built from user input.
.bxm templates
bxsapp.get( "/greet/:name", ( req, res ) => {
res.render( "greeting", { name: req.params.name } )
} )
html<!-- views/greeting.bxm -->
<bx:output><h1>Hello, #encodeForHTML( data.name )#!</h1></bx:output>
.bxm is BoxLang's native server-page format (think .cfm). data is whatever struct you pass as render()'s second argument, and interpolation only happens inside a <bx:output> block, same as classic CFML <cfoutput>. A view name with no extension gets .bxm appended.
Output isn't escaped for you: wrap anything that came from a user in encodeForHTML().
.hbs views, and the vendored handlebars-4.3.1.jar that carried an unrelated advisory, are gone. Rendering an .hbs view now throws BoxExpress.UnsupportedViewEngine — convert those views to .bxm.Sharing layout with partials
.bxm templates are just BoxLang, so ordinary <bx:include> works for shared header/footer/nav — this documentation section is built exactly this way, with every page including the same head/foot partials:
html<!-- views/docs/some-page.bxm -->
<bx:include template="../partials/_head.bxm">
<bx:output>
<h1>Page content#data.title#</h1>
</bx:output>
<bx:include template="../partials/_foot.bxm">
Included paths resolve relative to the including template's own directory, same as a plain CFML/BoxLang include — no special wiring from the framework is needed.
{page_slug, title} topics from a static per-project registry and passes it, along with which one is current, into a single shared _docsShell.bxm shell. The shell loops the array to render the sidebar's active-state links, then interpolates the current page's pre-rendered HTML — pre-rendered once server-side through the markdown renderer, not <bx:include>d per page, since the content itself is stored as a .md file (read via fileRead()) rather than a .bxm file — the same "shared shell, dynamic content" pattern shown above, just sourced from a markdown file instead of an inline template.app.locals / res.locals
Two plain structs merged into every render() call's data, so values a route doesn't set explicitly — a site name, the current user, shared nav data — don't have to be threaded through every single res.render() call by hand. app.locals is set once, application-wide; res.locals is set per request (typically from middleware) and only lives for that one request/response cycle. Precedence, low to high: app.locals, then res.locals (overriding app.locals), then whatever you pass as render()'s own data argument (overriding both):
bxsapp.locals.siteName = "My Site"
app.use( ( req, res, next ) => {
res.locals.user = req.session.user ?: "guest"
next()
} )
app.get( "/", ( req, res ) => {
res.render( "home", {} ) // views/home.bxm sees data.siteName and data.user
} )
Quick debugging without a view
For a one-off inspection route, res.dump() skips views entirely and sends BoxLang's own rich HTML dump of any variable:
bxsapp.get( "/debug", ( req, res ) => {
res.dump( { req: req.query, session: req.session } )
} )