mcpField
B
boxlang-web-development

boxlang-web-development

github.com/ortus-boxlangDevelopment048 installsUpdated 1w ago
Repository

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

  1. 01Configure the Application.bx lifecycle
  2. 02Handle forms and HTTP requests
  3. 03Build REST endpoints
  4. 04Manage sessions and CSRF tokens
  5. 05Create SSE endpoints and render templates
  6. 06Configure MiniServer and CommandBox

Use cases

  1. 01Setting up a BoxLang web application
  2. 02Building REST routes and form handlers
  3. 03Adding session-based state and CSRF checks
  4. 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-development

After installing, type/boxlang-web-developmentin Claude Code to confirm it loaded.

Details

From the project README

BoxLang 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 servicesModern REST/JSON APIs
WSDL-defined contractsLightweight, fast communication
WS-Security requiredJSON preferred
Complex type mappingSimple HTTP calls

References

Comments

Markdown supported