About boxlang-web-development
boxlang-web-development is a skill for developers and AI agents building web applications with BoxLang. It covers application lifecycle setup, HTTP and REST patterns, sessions, security, rendering, and server deployment.
Key features
- 01Configure the Application.bx lifecycle
- 02Handle forms and HTTP requests
- 03Build REST endpoints
- 04Manage sessions and CSRF tokens
- 05Create SSE endpoints and render templates
- 06Configure MiniServer and CommandBox
Use cases
- 01Setting up a BoxLang web application
- 02Building REST routes and form handlers
- 03Adding session-based state and CSRF checks
- 04Deploying with MiniServer or CommandBox
Install
Copy the command and run it in a terminal.
npx skills add https:/ / github.com/ ortus-boxlang/ skills --skill boxlang-web-developmentAfter installing, type/boxlang-web-developmentin Claude Code to confirm it loaded.
Details
From the project READMEBoxLang Web Development
BoxLang is the AI-native software productivity platform for building, modernizing and running applications, with developers and AI agents working together.
Overview
BoxLang web applications revolve around Application.bx, which acts as the
application lifecycle controller. The runtime handles HTTP requests through a
well-defined lifecycle, with full support for REST, sessions, CSRF, SSE, and
both MiniServer (dev) and CommandBox (production) deployment.
For a deep dive into multi-app isolation, descriptor discovery, and advanced
Application.bx patterns, see the
application-descriptor skill.
Application.bx
Application.bx lives at the web root and configures the entire application:
// Application.bx
class {
// Application settings
this.name = "MyApp"
this.sessionManagement = true
this.sessionTimeout = createTimeSpan( 0, 2, 0, 0 ) // 2 hours
this.applicationTimeout = createTimeSpan( 1, 0, 0, 0 ) // 1 day
this.setClientCookies = true
// Java classpath additions
this.javaSettings = {
loadPaths: [ "lib/" ],
loadColdFusionClassPath: false,
reloadOnChange: false
}
// Datasource defaults
this.datasource = "mainDB"
// --- Lifecycle Events ---
// Fires once when the application first starts
boolean function onApplicationStart() {
application.version = "1.0.0"
application.config = loadConfig()
return true
}
// Fires before every request
boolean function onRequestStart( required string targetPage ) {
// Auth checks, request logging, CORS headers
if ( !isAuthenticated() && !isPublicRoute( arguments.targetPage ) ) {
location( url="/login", addToken=false )
}
return true
}
// Fires after every request
void function onRequestEnd( required string targetPage ) {
// Cleanup, audit logging
}
// Called when request target is a .bx file (optional — allows custom routing)
void function onRequest( required string targetPage ) {
include arguments.targetPage
}
// Fires when a new session starts
boolean function onSessionStart() {
session.cart = []
session.userId = ""
session.isLoggedIn = false
return true
}
// Fires when a session ends
void function onSessionEnd( required struct sessionScope, required struct appScope ) {
auditService.recordLogout( arguments.sessionScope.userId )
}
// Global error handler
boolean function onError( required any exception, required string eventName ) {
errorService.log( arguments.exception )
include "views/error.bxm"
return true
}
// Missing template handler
boolean function onMissingTemplate( required string targetPage ) {
location( url="/404" )
return true
}
}
Request Scope and CGI Variables
// URL variables (?key=value)
var page = url.page ?: 1
var query = url.q ?: ""
// Form variables (POST body)
var username = form.username ?: ""
var password = form.password ?: ""
// CGI scope
var userAgent = cgi.http_user_agent
var requestMethod = cgi.request_method // GET, POST, PUT, DELETE
var remoteAddr = cgi.remote_addr
var serverName = cgi.server_name
// Request scope (per-request shared storage)
request.startTime = getTickCount()
request.userId = session.userId
Handling Forms
// Process a form submission
if ( cgi.request_method == "POST" ) {
// Validate
if ( !len( trim( form.email ) ) ) {
request.errors.append( "Email is required" )
}
if ( !request.errors.len() ) {
userService.create({
email : form.email,
username : form.username,
password : hashPassword( form.password )
})
location( url="/dashboard", addToken=false )
}
}
REST API Patterns
// REST endpoint using onRequest routing in Application.bx
void function onRequest( required string targetPage ) {
var method = cgi.request_method
var path = cgi.path_info
var router = new Router()
router.get( "/api/users", "handlers.UsersHandler", "index" )
router.post( "/api/users", "handlers.UsersHandler", "create" )
router.get( "/api/users/:id", "handlers.UsersHandler", "show" )
router.put( "/api/users/:id", "handlers.UsersHandler", "update" )
router.delete( "/api/users/:id", "handlers.UsersHandler", "delete" )
router.dispatch( method, path )
}
// Handler: handlers/UsersHandler.bx
class {
function index() {
var users = userService.findAll()
renderJSON( users )
}
function show( required numeric id ) {
var user = userService.findById( arguments.id )
if ( isNull( user ) ) {
httpStatus( 404 )
renderJSON({ error: "Not found" })
return
}
renderJSON( user )
}
function create() {
var data = jsonDeserialize( getHTTPRequestData().content )
var user = userService.create( data )
httpStatus( 201 )
renderJSON( user )
}
}
// Helper functions
function renderJSON( required any data ) {
bx:header name="Content-Type" value="application/json";
writeOutput( jsonSerialize( arguments.data ) )
abort
}
function httpStatus( required numeric code ) {
bx:header statusCode=arguments.code;
}
HTTP Client
// Simple GET
var response = httpGet( "https://api.example.com/data" )
var data = jsonDeserialize( response.fileContent )
// Full bx:http request
bx:http url="https://api.example.com/users" method="POST" result="apiResponse" {
bx:httpparam type="header" name="Authorization" value="Bearer #token#"
bx:httpparam type="header" name="Content-Type" value="application/json"
bx:httpparam type="body" value=jsonSerialize({ name: "Ada", email: "[email protected]" })
}
var response = jsonDeserialize( apiResponse.fileContent )
// HTTP with query string
bx:http url="https://api.example.com/search" method="GET" result="searcharesult" {
bx:httpparam type="url" name="q" value="boxlang"
bx:httpparam type="url" name="limit" value="10"
}
// File download
bx:http url="https://example.com/report.pdf" method="GET" result="pdfResponse"
getAsBinary="yes"
fileWrite( expandPath( "./downloads/report.pdf" ), pdfResponse.fileContent )
Session Management
// Set session values
session.userId = user.getId()
session.username = user.getUsername()
session.isLoggedIn = true
session.cart = []
// Check session
if ( session.isLoggedIn ?: false ) {
// Authenticated
}
// Invalidate session on logout
sessionInvalidate()
location( url="/login", addToken=false )
// Extend session programmatically
sessionRotate()
CSRF Protection
// Generate CSRF token (stores in session)
var token = csrfGenerateToken( "loginForm" )
// In your form (bxm template):
// <input type="hidden" name="csrfToken" value="#csrfGenerateToken('loginForm')#">
// Validate on POST
if ( !csrfVerifyToken( form.csrfToken, "loginForm" ) ) {
httpStatus( 403 )
writeOutput( "Invalid CSRF token" )
abort
}
Server-Sent Events (SSE)
// SSE endpoint: sse/updates.bxs
bx:header name="Content-Type" value="text/event-stream";
bx:header name="Cache-Control" value="no-cache";
bx:header name="X-Accel-Buffering" value="no";
var i = 0
while ( i < 100 ) {
var data = getLatestUpdates()
writeOutput( "data: #jsonSerialize(data)##char(10)##char(10)#" )
flush
sleep( 1000 )
i++
}
Output and Rendering
// Include a template
include "views/header.bxm"
include "views/user/profile.bxm"
// Save output to variable
var rendered = ""
savecontent variable="rendered" {
include "views/email/welcome.bxm"
}
// Set response headers
cfheader( name="X-Custom-Header", value="myValue" )
cfcontent( type="application/pdf" )
// Redirect
location( url="/dashboard", addToken=false )
location( url="/login", statusCode=302 )
MiniServer Configuration (Development)
miniserver.json in the project root:
{
"host": "localhost",
"port": 8080,
"webroot": "./www",
"debug": true,
"warmUpURLs": ["/"],
"rewrites": {
"enable": true,
"config": "urlrewrite.xml"
}
}
Start MiniServer:
boxlang-miniserver
# or via CommandBox
box server start
CommandBox Server Configuration (Production)
server.json:
{
"name": "MyApp",
"web": {
"http": { "port": 8080 },
"https": { "port": 8443, "enable": true },
"webroot": "www"
},
"app": {
"cfengine": "boxlang",
"javaVersion": "openjdk21_jdk"
},
"jvm": {
"heapSize": "512m",
"maxHeapSize": "1024m",
"args": "-Duser.timezone=UTC"
},
"env": {
"DB_HOST": "localhost",
"DB_NAME": "myapp"
}
}
SOAP Web Services
BoxLang 1.8.0+ provides the soap() BIF for consuming SOAP 1.1/1.2 web services.
It automatically parses WSDL documents and converts SOAP XML responses to BoxLang
native types.
Basic SOAP Client
// Create client from WSDL URL
var ws = soap( "http://example.com/service.wsdl" )
// Invoke an operation with named parameters
var result = ws.invoke( "getCustomer", { customerId: 12345 } )
writeOutput( "Name: #result.customerName#" )
Client with Authentication and Timeout
var ws = soap( "http://example.com/service.wsdl" )
.withBasicAuth( "apiUser", "secret" )
.timeout( 30 )
var customer = ws.invoke( "getCustomer", { customerId: 42 } )
Custom Headers
var ws = soap( "http://secure.example.com/service.wsdl" )
.header( "X-API-Key", "abc123" )
.header( "X-Tenant-ID", "tenant001" )
.timeout( 45 )
var result = ws.invoke( "getData" )
Service Inspection (Discover Operations)
var ws = soap( "http://example.com/service.wsdl" )
// List all available operations
var operations = ws.getOperations()
operations.each( ( op ) -> writeOutput( op & "<br>" ) )
// Get input parameter details for an operation
var info = ws.getOperationInfo( "createOrder" )
info.inputParameters.each( ( param ) -> {
writeOutput( "#param.name# (#param.type#)<br>" )
})
Error Handling (SOAP Faults → BoxLang Exceptions)
try {
var ws = soap( "http://example.com/service.wsdl" )
var result = ws.invoke( "processOrder", { orderId: orderId } )
} catch ( "soap.Fault" e ) {
writeOutput( "SOAP fault: #e.message#" )
} catch ( "soap.ConnectionError" e ) {
writeOutput( "Connection failed: #e.message#" )
} catch ( any e ) {
writeOutput( "Unexpected error: #e.message#" )
}
When to use soap() vs bx:http:
Use soap() | Use bx:http / httpGet() |
|---|---|
| Enterprise/legacy SOAP services | Modern REST/JSON APIs |
| WSDL-defined contracts | Lightweight, fast communication |
| WS-Security required | JSON preferred |
| Complex type mapping | Simple HTTP calls |