๐Ÿ”ท Core trusted

boxlang-web-development

Use this skill when building BoxLang web applications: Application.bx lifecycle, request/response handling, sessions, forms, REST APIs, HTTP clients, routing, CSRF protection, Server-Sent Events, or configuring CommandBox/MiniServer.

$ npx skills add ortus-boxlang/skills/boxlang-developer/web-development
$ coldbox ai skills install ortus-boxlang/skills/boxlang-developer/web-development
๐Ÿ”— https://skills.boxlang.io/skills/raw/ortus-boxlang/skills/boxlang-developer~web-development

BoxLang Web Development

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.

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 = deserializeJSON( getHTTPRequestData().content )
        var user = userService.create( data )
        httpStatus( 201 )
        renderJSON( user )
    }

}

// Helper functions
function renderJSON( required any data ) {
    cfheader( name="Content-Type", value="application/json" )
    writeOutput( serializeJSON( arguments.data ) )
    abort
}

function httpStatus( required numeric code ) {
    cfheader( statusCode=arguments.code )
}

HTTP Client

// Simple GET
var response = httpGet( "https://api.example.com/data" )
var data     = deserializeJSON( 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=serializeJSON({ name: "Ada", email: "[email protected]" })
}

var response = deserializeJSON( apiResponse.fileContent )

// HTTP with query string
bx:http url="https://api.example.com/search" method="GET" result="searchResult" {
    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
cfheader( name="Content-Type",      value="text/event-stream" )
cfheader( name="Cache-Control",     value="no-cache" )
cfheader( name="X-Accel-Buffering", value="no" )

var i = 0
while ( i < 100 ) {
    var data = getLatestUpdates()
    writeOutput( "data: #serializeJSON(data)##chr(10)##chr(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 servicesModern REST/JSON APIs
WSDL-defined contractsLightweight, fast communication
WS-Security requiredJSON preferred
Complex type mappingSimple HTTP calls

References