All pages
Powered by GitBook
1 of 6

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Tutorial: Wheels, AJAX, and You

Using Wheels to develop web applications with AJAX features is a breeze. You have several options and tools at your disposal, which we'll cover in this chapter.

Wheels was designed to be as lightweight as possible, so this keeps your options fairly open for developing AJAX features into your application.

While there are several flavors of JavaScript libraries out there with AJAX support, we will be using the jQuery framework in this tutorial. Let's assume that you are fairly familiar with the basics of jQuery and know how to set it up.

For this tutorial, let's create the simplest example of all: a link that will render a message back to the user without refreshing the page.

A Simple Example

In this example, we'll wire up some simple JavaScript code that calls a Wheels action asynchronously. All of this will be done with basic jQuery code and built-in Wheels functionality.

First, let's make sure we've got an appropriate route setup. It might be you're still using the default wildcard() route which will create some default GET routes for the controller/action pattern, but we'll add a new route here just for practice. We are going to create a route named sayHello and direct it to the hello action of the say controller. There are two ways you could write this code a long hand method specifying the controller and action separately as well as a short hand method that combines the two into a single parameter.

The longhand way would look like:

/config/routes.cfm
mapper()
  .get(name="sayHello", controller="say", action="hello")
.end()

The shorthand method would look like:

/config/routes.cfm
mapper()
  .get(name="sayHello", to="say##hello")
.end()

You can decide which method you prefer. Both sets of code above are equivalent.

Then, let's create a link to a controller's action in a view file, like so:

/app/views/say/hello.cfm
<cfoutput>

<!--- View code --->
<h1></h1>
<p></p>

#linkTo(text="Alert me!", route="sayHello", id="alert-button")#

</cfoutput>

That piece of code by itself will work just like you expect it to. When you click the link, you will load the hello action inside the say controller.

But let's make it into an asynchronous request. Add this JavaScript (either on the page inside script tags or in a separate .js file included via javaScriptIncludeTag() ):

JavaScript
(function($) {
    // Listen to the "click" event of the "alert-button" link and make an AJAX request
    $("#alert-button").on("click", function(event) {
        $.ajax({
            type: "GET",
            // References "/say/hello?format=json"
            url: $(this).attr("href") + "?format=json",
            dataType: "json",
            success: function(response) {
                $("h1").html(response.message);
                $("p").html(response.time);
            }
        });

        event.preventDefault();
    });
})(jQuery);

With that code, we are listening to the click event of the hyperlink, which will make an asynchronous request to the hello action in the say controller. Additionally, the JavaScript call is passing a URL parameter called format set to json.

Note that the success block inserts keys from the response into the empty h1 and p blocks in the calling view. (You may have been wondering about those when you saw the first example. Mystery solved.)

The last thing that we need to do is implement the say/hello action. Note that the request expects a dataType of JSON. By default, Wheels controllers only generate HTML responses, but there is an easy way to generate JSON instead using Wheels's provides() and renderWith() functions:

/app/controllers/Say.cfc
component extends="Controller" {
    function config() {
        provides("html,json");
    }

    function hello() {
        // Prepare the message for the user.
        greeting = {};
        greeting["message"] = "Hi there";
        greeting["time"] = Now();

        // Respond to all requests with `renderWith()`.
        renderWith(greeting);
    }
}

In this controller's config() method, we use the provides() function to indicate that we want all actions in the controller to be able to respond with the data in HTML or JSON formats. Note that the client calling the action can request the type by passing a URL parameter named format or by sending the format in the request header.

The call to renderWith() in the hello action takes care of the translation to the requested format. Our JavaScript is requesting JSON, so Wheels will format the greeting struct as JSON automatically and send it back to the client. If the client requested HTML or the default of none, Wheels will process and serve the view template at /app/views/say/hello.cfm. For more information about provides() and renderWith(), reference the chapter on Responding with Multiple Formats.

Lastly, notice the lines where we're setting greeting["message"] and greeting["time"]. Due to the case-insensitive nature of ColdFusion, we recommend setting variables to be consumed by JavaScript using bracket notation like that. If you do not use that notation (i.e., greetings.message and greetings.time instead), your JavaScript will need to reference those keys from the JSON as MESSAGE and TIME (all caps). Unless you like turning caps lock on and off, you can see how that would get annoying after some time.

Assuming you already included jQuery in your application and you followed the code examples above, you now have a simple AJAX-powered web application built on Wheels. After clicking that Alert me! link, your say controller will respond back to you the serialized message via AJAX. jQuery will parse the JSON object and populate the h1 and pwith the appropriate data.

AJAX in Wheels Explained

That is it! Hopefully now you have a clearer picture on how to create AJAX-based features for your web applications.

Running Local Development Servers

Wheels uses a Docker-based development environment that provides consistent, containerized development with support for multiple CFML engines and databases.

Prerequisites

  1. Docker: Install Docker Desktop from docker.com

  2. Wheels CLI: Install the Wheels CommandBox module:

    box install wheels-cli

Setting up Docker Development Environment

Ensure you are in the application root directory.

Initialize your Docker development environment:

wheels docker init cfengine=adobe cfversion=2018 db=mysql

Command Options

cfengine options:

  • lucee - Lucee CFML engine

  • adobe - Adobe ColdFusion

  • boxlang - BoxLang CFML engine

cfversion options:

  • Major versions for Adobe ColdFusion: 2018, 2021, 2023, 2025

  • Major versions for Lucee: 5, 6, 7

  • Major versions for BoxLang: 1

db options:

  • mysql - MySQL database

  • postgres - PostgreSQL database

  • mssql - Microsoft SQL Server

  • h2 - H2 embedded database

Generated Files

The wheels docker init command creates several files in your project:

  • .dockerignore - Specifies files to exclude from Docker build context

  • Dockerfile - Container definition for your chosen CFML engine

  • docker-compose.yml - Multi-container application definition

  • CFConfig.json - CFML engine configuration with datasource setup

Starting Your Development Environment

After running the init command, start your containers:

docker-compose up -d

The containers will take a few minutes to start the first time as Docker downloads the necessary images. Once started, your application will be available at:

  • Default: http://localhost:8080

  • Custom port: Check your server.json file for the configured port

Managing Your Docker Environment

# Stop the containers
docker-compose down

# View running containers
docker-compose ps

# View container logs
docker-compose logs

# Rebuild and restart
docker-compose up -d --build

Additional Configuration

Custom Ports

The default port is 8080, but you can customize this by modifying the server.json:

{
    "name":"wheels",
    "web":{
        "host":"localhost",
        "http":{
            "port":3000
        },
        "webroot":"public",
        "rewrites":{
            "enable":true,
            "config":"public/urlrewrite.xml"
        }
    }
}

Database Configuration

The generated CFConfig.json file automatically configures a datasource for your chosen database. The configuration includes:

  • Connection settings for your selected database type

  • Default datasource named wheels-dev

  • Appropriate drivers for the database engine

Development Workflow

  1. Make code changes in your directory

  2. Changes are reflected immediately due to Docker volume mounting

  3. Database changes persist between container restarts

  4. Use standard Wheels commands like migrations, generators, etc.

Troubleshooting

Containers won't start:

# Check if ports are in use
docker-compose ps
netstat -an | grep 8080

# Force recreate containers
docker-compose down
docker-compose up -d --force-recreate

Database connection issues:

# Check database container logs
docker-compose logs db

# Restart just the database
docker-compose restart db

Performance issues:

  • Ensure Docker Desktop has adequate memory allocated (4GB+ recommended)

  • On Windows/Mac, enable file sharing for your project directory

CFIDE / Lucee administrators

The default username and password for all administrators is admin & commandbox

Getting Started

Install Wheels and get a local development server running

By far the quickest way to get started with Wheels is via . CommandBox brings a whole host of command line capabilities to the CFML developer. It allows you to write scripts that can be executed at the command line written entirely in CFML. It allows you to start a CFML server from any directory on your machine and wire up the code in that directory as the web root of the server. What's more is, those servers can be either Lucee servers or Adobe ColdFusion servers. You can even specify what version of each server to launch. Lastly, CommandBox is a package manager for CFML. That means you can take some CFML code and package it up into a module, host it on ForgeBox.io, and make it available to other CFML developers. In fact we make extensive use of these capabilities to distribute Wheels plugins and templates. More on that later.

One module that we have created is a module that extends CommandBox itself with commands and features specific to the Wheels framework. The Wheels CLI module for CommandBox is modeled after the Ruby on Rails CLI module and gives similar capabilities to the Wheels developer.

Install CommandBox

The first step is to get downloaded and running. CommandBox is available for Windows, Mac & Linux, and can be installed manually or using one of the respective package managers for each OS. You can use on Windows, on MacOS, or Yum/Apt on Linux depending on your flavor of Linux. Please follow the instructions on how to install CommandBox on your particular operating system. At the end of the installation process you want to make sure the box command is part of your system path so you can call the command from any directory on your system.

Once installed, you can either double-click on the box executable which opens the CommandBox shell window, or run box from a CMD window in Windows, Terminal window in MacOS, or shell prompt on a Linux server. Sometimes you only want to call a single CommandBox command and don't need to launch a whole CommandBox shell window to do that, for these instances you can call the CommandBox command directly from your default system terminal window by prefixing the command with the box prefix.

So to run the CommandBox version command you could run box version from the shell or you could launch the CommandBox shell and run version inside it.

box version

version

This is a good concept to grasp, cause depending on your workflow, you may find it easier to do one versus the other. Most of the commands you will see in these CLI guides will assume that you are entering the command in the actual CommandBox shell so the box prefix is left off.

Install the wheels-cli CommandBox Module

Okay, now that we have CommandBox installed, let's add the Wheels CLI module.

install wheels-cli

Installing this module will add a number of commands to your default CommandBox installation. All of these commands are prefixed by the wheels name space. There are commands to create a brand new Wheels application or scaffold out sections of your application. We'll see some of these commands in action momentarily.

Start a new Application using the Wizard

To install a new application using version 3.0, we can use the new application wizard and select Bleeding Edge when prompted to select the template to use.

wheels new

Start a New Application Using the Command Line

Now that we have CommandBox installed and extended it with the Wheels CLI module, let's start our first Wheels app from the command line. We'll look at the simplest method for creating a Wheels app and starting our development server.

wheels generate app myApp server start

A few minutes after submitting the above commands a new browser window should open up and display the default Wheels congratulations screen.

So what just happened? Since we only passed the application name myApp to the wheels generate app command, it used default values for most of its parameters and downloaded our Base template (wheels-base-template) from ForgeBox.io, then downloaded the framework core files (wheels.dev) from ForgeBox.io and placed it in the vendor/wheels directory, then configured the application name and reload password, and started a Lucee server on a random port.

A Word About Command Aliases

CommandBox commands have the capability to be called by multiple names or aliases. The command above wheels generate app can also be initiated by typing wheels g app. In fact g is an alias for generate so wherever you see a command in the CLI documentation that has generate in it you can substitute g instead.

In addition to shortening generate to g, aliases can completely change the name space as well. A command that you haven't seen yet is the wheels generate app-wizard command. This command guides the user through a series of menu options, building up all the parameters needed to customize the start of a new Wheels project. You're likely to use the wizard when starting a new Wheels application so it's good to become familiar with it.

This command has the normal alias referenced above at wheels g app-wizard but it also has an additional alias at wheels new which is the command more prevalent in the Rails community. So the three commands wheels generate app-wizard, wheels g app-wizard, and wheels new all call the same functionality which guides the user though a set of menus, collecting details on how to configure the desired app. Once all the parameters have been gathered, this command actually calls the wheels generate app command to create the actual Wheels application.

This Getting Started guide has taken you from the very beginning and gotten you to the point where you can go into any empty directory on your local development machine and start a Wheels project by issuing a couple of CLI commands. In later guides we'll explore these options further and see what else the CLI can do for us.

CommandBox
CommandBox
Chocolatey
Homebrew
Figure: Wheels congratulations screen

Beginner Tutorial: Hello World

In this tutorial, we'll be writing a simple application to make sure we have Wheels installed properly and that everything is working as it should.

Testing Your Install

Let's make sure we're all on the same page. I'm going to assume that you've followed the Getting Started guide and have CommandBox all setup. If you haven't done that, stop and read that guide get everything setup. It's okay, this web page will wait for you.

Okay, so you have Wheels installed and can see the Wheels "Congratulations!" page as shown below. That wasn't that hard now, was it?

Figure 1: Wheels congratulations screen

Hello World: Your First Wheels App

Okay, let's get to some example code. We know that you've been dying to get your hands on some code!

To continue with Programming Tutorial Tradition, we'll create the ubiquitous Hello World! application. But to keep things interesting, let's add a little Wheels magic along the way.

Setting up the Controller

Let's create a controller from scratch to illustrate how easy it is to set up a controller and plug it into the Wheels framework.

First, create a file called Say.cfc in the /app/controllers directory and add the code below to the file.

/app/controllers/Say.cfc
component extends="Controller"{
}

Congratulations, you just created your first Wheels controller! What does this controller do, you might ask? Well, to be honest, not much. It has no methods defined, so it doesn't add any new functionality to our application. But because it extends the base Controller component, it inherits quite a bit of powerful functionality and is now tied into our Wheels application.

So what happens if we try to call our new controller right now? Lets take a look. Open your browser and point your browser to the new controller. Because my local server is installed on port 60000, my URL is http://127.0.0.1:60000/say. You may need to enter a different URL, depending on how your web server is configured. In my case, I'm using CommandBox.

Figure 2: Wheels error after setting up your blank say controller

The error says "Could not find the view page for the 'index' action in the 'say' controller." Where did "index" come from? The URL we typed in only specified a controller name but no action. When an action is not specified in the URL, Wheels assumes that we want the default action. Out of the box, the default action in Wheels is set to index. So in our example, Wheels tried to find the index action within the say controller, and it threw an error because it couldn't find its view page.

Setting up an Action

But let's jump ahead. Now that we have the controller created, let's add an action to it called hello. Change your say controller so it looks like the code block below:

/app/controllers/Say.cfc
component extends="Controller" {
    function hello() {
    }
}

As you can see, we created an empty method named hello.

Now let's call our new action in the browser and see what we get. To call the hello action, we simply add /hello to the end of the previous URL that we used to call our say controller:

http://127.0.0.1:60000/say/hello

Once again, we get a ColdFusion error. Although we have created the controller and added the hello action to it, we haven't created the view.

Setting up the View

By default, when an action is called, Wheels will look for a view file with the same name as the action. It then hands off the processing to the view to display the user interface. In our case, Wheels tried to find a view file for our say/hello action and couldn't find one.

Let's remedy the situation and create a view file. View files are simple CFML pages that handle the output of our application. In most cases, views will return HTML code to the browser. By default, the view files will have the same name as our controller actions and will be grouped into a directory under the view directory. This new directory will have the same name as our controller.

Find the views directory inside the app directory, located at / in your Wheels installation. There will be a few directories in there already. For now, we need to create a new directory in the views directory called say. This is the same name as the controller that we created above.

Now inside the say directory, create a file called hello.cfm. In the hello.cfm file, add the following line of code:

/app/views/say/hello.cfm
<h1>Hello World!</h1>

Save your hello.cfm file, and let's call our say/hello action once again. You have your first working Wheels page if your browser looks like Figure 3 below.

Figure 3: Your first working Wheels action.

You have just created your first functional Wheels page, albeit it is a very simple one. Pat yourself on the back, go grab a snack, and when you're ready, let's go on and extend the functionality of our Hello World! application a little more.

Adding Dynamic Content to Your View

We will add some simple dynamic content to our hello action and add a second action to the application. We'll then use some Wheels code to tie the 2 actions together. Let's get get to it!

The Dynamic Content

The first thing we are going to do is to add some dynamic content to our say/hello action. Modify your say controller so it looks like the code block below:

/app/controllers/Say.cfc
component extends="Controller" {
    function hello() {
        time = Now();
    }
}

All we are doing here is creating a variable called time and setting its value to the current server time using the basic ColdFusion Now() function. When we do this, the variable becomes immediately available to our view code.

Why not just set up this value directly in the view? If you think about it, maybe the logic behind the value of time may eventually change. What if eventually we want to display its value based on the user's time zone? What if later we decide to pull it from a web service instead? Remember, the controller is supposed to coordinate all of the data and business logic, not the view.

Displaying the Dynamic Content

Next, we will modify our say/hello.cfm view file so that it looks like the code block below. When we do this, the value will be displayed in the browser.

/app/views/say/hello.cfm
<h1>Hello World!</h1>
<p>Current time: <cfoutput>#time#</cfoutput></p>

call your say/hello action again in your browser. Your browser should look like Figure 4 below.

Figure 4: Hello World with the current date and time

This simple example showed that any dynamic content created in a controller action is available to the corresponding view file. In our application, we created a time variable in the say/hello controller action and display that variable in our say/hello.cfm view file.

Adding a Second Action: Goodbye

Now we will expand the functionality of our application once again by adding a second action to our say controller. If you feel adventurous, go ahead and add a goodbye action to the say controller on your own, then create a goodbye.cfm view file that displays a "Goodbye" message to the user. If you're not feeling that adventurous, we'll quickly go step by step.

First, modify the the say controller file so that it looks like the code block below.

/app/controllers/Say.cfc
component extends="Controller" {
    function hello() {
        time = Now();
    }

    function goodbye() {
    }
}

Now go to the /app/views/say directory and create a goodbye.cfm page.

Add the following code to the goodbye.cfm page and save it.

/app/views/say/goodbye.cfm
Goodbye World!

If we did everything right, we should be able to call the new say/goodbye action using the following URL:

http://127.0.0.1:60000/say/goodbye

Your browser should look like Figure 5 below:

Figure 5: Your new goodbye action

Linking to Other Actions

Now let's link our two actions together. We will do this by adding a link to the bottom of each page so that it calls the other page.

Linking Hello to Goodbye

Open the say/hello.cfm view file. We are going to add a line of code to the end of this file so our say/hello.cfm view file looks like the code block below:

/app/views/say/hello.cfm
<h1>Hello World!</h1>
<p>Current time: <cfoutput>#time#</cfoutput></p>
<p>Time to say <cfoutput>#linkTo(text="goodbye", action="goodbye")#?</cfoutput></p>

The linkTo() function is a built-in Wheels function. In this case, we are passing 2 named parameters to it. The first parameter, text, is the text that will be displayed in the hyperlink. The second parameter, action, defines the action to point the link to. By using this built-in function, your application's main URL may change, and even controllers and actions may get shifted around, but you won't suffer from the dreaded dead link. Wheels will always create a valid link for you as long as you configure it correctly when you make infrastructure changes to your application.

Once you have added the additional line of code to the end of the say/hello.cfm view file, save your file and call the say/hello action from your browser. Your browser should look like Figure 6 below.

Figure 6: Your say/hello action with a link to the goodbye action

You can see that Wheels created a link for us and added an appropriate URL for the say/goodbye action to the link.

Linking Goodbye to Hello

Let's complete our little app and add a corresponding link to the bottom of our say/goodbye.cfm view page.

Open your say/goodbye.cfm view page and modify it so it looks like the code block below.

/app/views/say/goodbye.cfm
<h1>Goodbye World!</h1>
<p>Time to say <cfoutput>#linkTo(text="hello", action="hello")#?</cfoutput></p>

If you now call the say/goodbye action in your browser, your browser should look like Figure 7 below.

Figure 7: Your say/goodbye action with a link to the hello action

Much More to Learn

You now know enough to be dangerous with Wheels. Look out! But there are many more powerful features to cover. You may have noticed that we haven't even talked about the M in MVC.

No worries. We will get there. And we think you will enjoy it.

Boxlang Support

Wheels supports BoxLang 1.x, providing developers with a modern, high-performance CFML runtime. You can run BoxLang applications using either CommandBox or BoxLang Mini-Server.

Prerequisites

  • Java 21 or higher installed on your system

  • Wheels application (generated or existing)

  • BoxLang runtime (see installation options below)

Method 1: Using CommandBox (Recommended)

CommandBox provides the easiest and most feature-rich way to run BoxLang applications with Wheels.

Installation and Setup

# Install BoxLang engine in CommandBox
box install boxlang

# Start server with BoxLang
box server start cfengine=boxlang

# Or specify specific BoxLang version (optional)
box server start [email protected]

BoxLang Module Dependencies

BoxLang requires specific modules for full Wheels compatibility. These dependencies should be added to your box.json file:

{
  "dependencies": {
    "bx-compat-cfml": "^1.27.0+35",
    "bx-csrf": "^1.2.0+3", 
    "bx-esapi": "^1.6.0+9",
    "bx-image": "^1.0.1",
    "bx-wddx":"^1.5.0+8",
    "bx-mysql": "^1.0.1+7"
  }
}

Installing Dependencies

# Install all dependencies from box.json
box install

# Or install specific BoxLang modules individually
box install bx-compat-cfml
box install bx-csrf
box install bx-esapi
box install bx-image
box install bx-wddx
box install bx-mysql

Module Descriptions

  • bx-compat-cfml - CFML compatibility layer for BoxLang

  • bx-csrf - Cross-Site Request Forgery protection

  • bx-esapi - Enterprise Security API for input validation

  • bx-image - Image manipulation functionality

  • bx-wddx - Web Distributed Data eXchange (WDDX) conversion

  • bx-mysql - MySQL database driver

Additional Database Support

For other databases supported by Wheels, install the corresponding BoxLang modules:

  • Microsoft SQL Server: box install bx-mssql

  • PostGreSQL Server: box install bx-postgresql

  • Oracle Server: box install bx-oracle

Finding Additional Modules

For any additional functionality or database drivers not included in the core dependencies:

  • Browse ForgeBox: Visit forgebox.io

  • Search for BoxLang modules: Look for modules with bx- prefix

  • Copy install command: Each module page provides the exact box install command

  • Install the module: Run the command in your project directory

Example: For Microsoft SQL Server support, visit the bx-mssql module page on ForgeBox and copy the installation command.

Server Configuration

Create a server.json file in your application root to persist BoxLang settings:

{
    "name":"wheels",
    "web":{
        "host":"localhost",
        "http":{
            "port":3000
        },
        "webroot":"public",
        "rewrites":{
            "enable":true,
            "config":"public/urlrewrite.xml"
        }
    },
    "app": {
        "cfengine": "boxlang"
    }
}

Development Workflow

# Generate new Wheels app (if needed)
wheels g app myapp --engine=boxlang

# Navigate to app directory
cd myapp

# Install BoxLang dependencies
box install

# Start BoxLang development server  
server start cfengine=boxlang

# Access your application at http://localhost:8080

Method 2: Using BoxLang Mini-Server

BoxLang Mini-Server provides a lightweight, standalone option perfect for minimal setups or specific deployment scenarios.

Installation

BoxLang Mini-Server can be downloaded directly from the official BoxLang releases. The latest version is fully compatible with Wheels.

Download the latest BoxLang Mini-Server:

# Download the latest BoxLang Mini-Server JAR file
curl -LO https://downloads.ortussolutions.com/ortussolutions/boxlang-runtimes/boxlang-miniserver/boxlang-miniserver-latest.jar

Installation Steps

  1. Download BoxLang Mini-Server Package (optional, for additional files)

    # Download complete package with additional files
    curl -LO https://downloads.ortussolutions.com/ortussolutions/boxlang-runtimes/boxlang-miniserver/boxlang-miniserver-latest.zip
    unzip boxlang-miniserver.zip
  2. Prepare Your Application Structure

    your-wheels-app/
    ├── config/           # Configuration files
    ├── app/             # Controllers, models, views
    ├── public/          # Web-accessible files
    │   ├── index.bxm    # BoxLang entry point (required)
    │   ├── stylesheets/
    │   ├── javascripts/
    │   └── images/
    └── vendor/wheels/   # Wheels framework files
  3. Setup BoxLang Entry Point

    Create an index.bxm file in your public/ folder with the following content:

    <bx:script>
    // BoxLang rewrite handler for Wheels
    // This file handles URL rewriting for BoxLang compatibility
    
    // Get the requested URI
    requestURI = cgi.request_uri ?: "";
    
    // Remove query string for pattern matching  
    if (find("?", requestURI)) {
        requestURI = listFirst(requestURI, "?");
    }
    
    // Handle requests that come through /index.bxm/path - redirect to clean URL
    if (find("/index.bxm/", requestURI)) {
        cleanPath = replace(requestURI, "/index.bxm", "");
        queryString = cgi.query_string ?: "";
        redirectURL = cleanPath;
        if (len(queryString)) {
            redirectURL &= "?" & queryString;
        }
        bx:header name="Location" value=redirectURL;
        bx:header statusCode=301;
        bx:abort;
    }
    
    // Static paths that should not be rewritten (based on urlrewrite.xml)
    staticPaths = "cf_script,flex2gateway,jrunscripts,CFIDE,lucee,cfformgateway,cffileservlet,files,images,javascripts,miscellaneous,stylesheets,wheels/public/assets";
    specialFiles = "robots.txt,favicon.ico,sitemap.xml,index.cfm";
    
    // Check if this is a static path or special file
    isStatic = false;
    
    if (len(requestURI) && requestURI != "/") {
        cleanPath = right(requestURI, len(requestURI) - 1); // Remove leading slash
        
        // Check special files first
        fileName = listLast(requestURI, "/");
        if (listFindNoCase(specialFiles, fileName)) {
            isStatic = true;
        }
        
        // Check static paths
        if (!isStatic) {
            for (staticPath in listToArray(staticPaths)) {
                if (left(cleanPath, len(staticPath)) == staticPath) {
                    isStatic = true;
                    break;
                }
            }
        }
        
        // Check file extensions for static files
        if (!isStatic && listLen(cleanPath, ".") > 1) {
            extension = listLast(cleanPath, ".");
            staticExtensions = "js,css,png,jpg,jpeg,gif,ico,pdf,zip,txt,xml,json";
            if (listFindNoCase(staticExtensions, extension)) {
                isStatic = true;
            }
        }
    }
    
    // If it's a static file, let it pass through
    if (isStatic) {
        bx:header statusCode=404;
        writeOutput("File not found");
        bx:abort;
    }
    
    // Set up CGI variables for clean URL generation
    if (len(requestURI) && requestURI != "/") {
        cgi.path_info = requestURI;
    }
    
    // Override script name for clean URL generation
    cgi.script_name = "/index.cfm";
    
    // Clean up request URI
    if (find("/index.bxm", cgi.request_uri ?: "")) {
        cgi.request_uri = replace(cgi.request_uri, "/index.bxm", "");
    }
    
    // Update request scope for Wheels compatibility
    if (structKeyExists(request, "cgi")) {
        request.cgi.script_name = "/index.cfm";
        if (structKeyExists(request.cgi, "request_uri") && find("/index.bxm", request.cgi.request_uri)) {
            request.cgi.request_uri = replace(request.cgi.request_uri, "/index.bxm", "");
        }
    }
    </bx:script>
    
    <!--- Include the main Wheels bootstrap file --->
    <bx:include template="index.cfm" />

    This file serves as the BoxLang-specific entry point that handles URL rewriting and bootstraps your Wheels application.

Starting BoxLang Mini-Server

Basic Command

java -jar /path/to/boxlang-miniserver-1.6.0.jar \
  --webroot /path/to/your/app/public \
  --rewrite

Full Configuration Example

java -jar /path/to/boxlang-miniserver-1.6.0.jar \
  --webroot /path/to/your/app/public \
  --host 127.0.0.1 \
  --port 8080 \
  --rewrite \
  --debug

For Wheels Template Structure

If using the Wheels base template structure:

java -jar /path/to/boxlang-miniserver-1.6.0.jar \
  --webroot /path/to/your/app/templates/base/src/public \
  --rewrite \
  --port 8080

Mini-Server Command Options

Option
Description
Default

--webroot

Document root directory (required)

None

--host

IP address to bind to

0.0.0.0

--port

Port number

8080

--rewrite

Enable URL rewriting (recommended for Wheels)

false

--debug

Enable debug mode

false

--config

Path to configuration file

None

--libs

Additional library paths

None

You can read the further details from the boxlang mini-server documentation

Troubleshooting

Common Issues

  1. Missing BoxLang Dependencies (CommandBox)

    • Problem: Functions or features not working, missing module errors

    • Solution: Ensure all required BoxLang modules are installed: box install

    • Check: Verify box.json contains all required bx-* dependencies

  2. Missing index.bxm File (Mini-Server)

    • Problem: Server returns 404 or directory listing

    • Solution: Create index.bxm in your public/ folder using the complete file content provided above in the Setup steps

  3. URL Routing Not Working

    • Problem: Routes return 404 errors

    • Solution: Always include the --rewrite flag when starting Mini-Server

  4. Version Compatibility Issues

    • Problem: Unexpected errors or features not working

    • Solution: Verify you're using a recent version of BoxLang 1.x

  5. Path Resolution Problems

    • Problem: Files not found or incorrect paths

    • Solution: Use absolute paths to avoid directory resolution issues

Testing Your Setup

# Verify server is responding
curl http://localhost:8080

# Test Wheels is loading
curl http://localhost:8080/wheels

# Check specific routes
curl http://localhost:8080/say/hello

Recommendation

For most developers, CommandBox with BoxLang provides the best experience with automatic updates, dependency management, and integrated tooling. Use BoxLang Mini-Server for specialized deployment scenarios or minimal footprint requirements.

Beginner Tutorial: Hello Database

A quick tutorial that demonstrates how quickly you can get database connectivity up and running with Wheels.

Wheels's built in model provides your application with some simple and powerful functionality for interacting with databases. To get started, you will make some simple configurations, call some functions within your controllers, and that's it. Best yet, you will rarely ever need to write SQL code to get those redundant CRUD tasks out of the way.

Our Sample Application: User Management

We'll learn by building part of a sample user management application. This tutorial will teach you the basics of setting up a resource that interacts with the Wheels ORM.

Setting up the Data Source

By default, Wheels will connect to a data source wheels-dev. To change this default behavior, open the file at /config/settings.cfm. In a fresh install of Wheels, you'll see the follwing code:

These lines provide Wheels with the necessary information about the data source, URL rewriting, and reload password for your application, and include the appropriate values. This may include values for dataSourceName, dataSourceUserName, and dataSourcePassword. More on URL rewriting and reload password later.

Datasource Configuration Methods

In Wheels applications, you typically configure which datasource to use in /config/settings.cfm. The actual datasource definitions should be added in /config/app.cfm.

Wheels will look for the datasource defined in settings.cfm and match it against what you’ve defined in app.cfm or through your CFML engine’s administrator.


Option 1: Datasource Configuration via Administrator (Adobe & Lucee)

You can manage datasources through the Administrator interface of your CFML engine:

  1. Access your CFML administrator (Adobe ColdFusion Administrator or Lucee Server/Web Administrator).

  2. Navigate to the Datasource section.

  3. Create a new datasource with the exact same name as the one you’ve set in settings.cfm (dataSourceName).

  4. Provide connection details (JDBC driver, connection string, username, password).

  5. Configure optional features like connection pooling and validation queries.

This method lets you manage database connectivity centrally in the engine’s admin console.


Option 2: Datasource Configuration via /config/app.cfm (Lucee & BoxLang)

You can also define datasources programmatically in your Wheels application using the this.datasources struct inside /config/app.cfm. This approach works in Lucee and BoxLang without needing Administrator access.

Regardless of which method you choose, ensure that the datasource name and configuration in your Wheels configuration matches exactly with the datasource created in your CFML engine.

Our Sample Data Structure

Wheels supports MySQL, SQL Server, PostgreSQL, Oracle and H2. It doesn't matter which DBMS you use for this tutorial; we will all be writing the same CFML code to interact with the database. Wheels does everything behind the scenes that needs to be done to work with each DBMS.

That said, here's a quick look at a table that you'll need in your database, named users:

Column Name
Data Type
Extra

Note a couple things about this users table:

  1. The table name is plural.

  2. The table has an auto-incrementing primary key named id.

These are database used by Wheels. This framework strongly encourages that everyone follow convention over configuration. That way everyone is doing things mostly the same way, leading to less maintenance and training headaches down the road.

Fortunately, there are ways of going outside of these conventions when you really need it. But let's learn the conventional way first. Sometimes you need to learn the rules before you can know how to break them.

Creating Routes for the users Resource

Next, open the file at /config/routes.cfm. You will see contents similar to this:

We are going to create a section of our application for listing, creating, updating, and deleting user records. In Wheels routing, this requires a plural resource, which we'll name users.

Because a users resource is more specific than the "generic" routes provided by Wheels, we'll list it first in the chain of mapper method calls:

This will create URL endpoints for creating, reading, updating, and deleting user records:

Name
Method
URL Path
Description
  • Name is referenced in your code to tell Wheels where to point forms and links.

  • Method is the HTTP verb that Wheels listens for to match up the request.

  • URL Path is the URL that Wheels listens for to match up the request.

Don't forget to reload

You will need to reload your application after adding new routes!

Creating Users

First, let's create a simple form for adding a new user to the users table. To do this, we will use Wheels's form helper functions. Wheels includes a whole range of functions that simplifies all of the tasks that you need to display forms and communicate errors to the user.

Creating the Form

Now create a new file in /app/views/users called new.cfm. This will contain the view code for our simple form.

Next, add these lines of code to the new file:

Form Helpers

What we've done here is use form helpers to generate all of the form fields necessary for creating a new user in our database. It may feel a little strange using functions to generate form elements, but it will soon become clear why we're doing this. Trust us on this one… you'll love it!

To generate the form tag's action attribute, the function takes parameters similar to the function that we introduced in the Beginner Tutorial: Hello World tutorial. We can pass in controller, action, key, and other route- and parameter-defined URLs just like we do with .

To end the form, we use the function. Easy enough.

The and helpers are similar. As you probably guessed, they create <input> elements with type="text" and type="password", respectively. And the function creates an <input type="submit" /> element.

One thing you'll notice is the and functions accept arguments called objectName and property. As it turns out, this particular view code will throw an error because these functions are expecting an object named user. Let's fix that.

Supplying the Form with Data

All of the form helper calls in our view specify an objectName argument with a reference to a variable named user. That means that we need to supply our view code with an object called user. Because the controller is responsible for providing the views with data, we'll set it there.

Create a new ColdFusion component at /app/controllers/Users.cfc.

As it turns out, our controller needs to provide the view with a blank user object (whose instance variable will also be called user in this case). In our new action, we will use the function to generate a new instance of the user model.

To get a blank set of properties in the model, we'll also call the generated model's method.

Wheels will automatically know that we're talking about the users database table when we instantiate a user model. The convention: database tables are plural and their corresponding Wheels models are singular.

Why is our model name singular instead of plural? When we're talking about a single record in the users database, we represent that with an individual model object. So the users table contains many user objects. It just works better in conversation.

The Generated Form

Now when we run the URL at http://localhost:8080/users/new, we'll see the form with the fields that we defined.

The HTML generated by your application will look something like this:

So far we have a fairly well-formed, accessible form, without writing a bunch of repetitive markup.

Handling the Form Submission

Next, we'll code the create action in the controller to handle the form submission and save the new user to the database.

A basic way of doing this is using the model object's method:

Because we used the objectName argument in the fields of our form, we can access the user data as a struct in the params struct.

There are more things that we can do in the create action to handle validation, but let's keep it simple in this tutorial.

Listing Users

Notice that our create action above redirects the user to the users index route using the function. We'll use this action to list all users in the system with "Edit" links. We'll also provide a link to the "New User" form that we just coded.

First, let's get the data that the listing needs. Create an action named index in the users controller like so:

This call to the model's method will return a query object of all users in the system. By using the method's order argument, we're also telling the database to order the records by username.

In the view at /app/views/users/index.cfm, it's as simple as looping through the query and outputting the data

When to use EncodeForHtml

You'll see references to EncodeForHtml in some of our examples that output data. This helps escape HTML code in data that attackers could use to embed inject harmful JavaScript. (This is commonly referred to as an "XSS attack," short for "Cross-site Scripting attack.")

A rule of thumb: you do not need to use EncodeForHtml when passing values into Wheels helpers like linkTo, buttonTo, startFormTag, textField, etc. However, you need to escape data that is displayed directly onto the page without a Wheels helper.

Editing Users

We'll now show another cool aspect of form helpers by creating a screen for editing users.

Coding the Edit Form

You probably noticed in the code listed above that we'll have an action for editing a single users record. We used the form helper function to add an "Edit" button to the form. This action expects a key as well.

Because in the form helper function we specified the parameter key, Wheels adds this parameter into the URL when generating the route.

Wheels will automatically add the provided 'key' from the URL to the params struct in the controllers edit() function.

Given the provided key, we'll have the action load the appropriate user object to pass on to the view:

The view at /app/views/users/edit.cfm looks almost exactly the same as the view for creating a user:

But an interesting thing happens. Because the form fields are bound to the user object via the form helpers' objectName arguments, the form will automatically provide default values based on the object's properties.

With the user model populated, we'll end up seeing code similar to this:

Pretty cool, huh?

Opportunities for Refactoring

There's a lot of repetition in the new and edit forms. You'd imagine that we could factor out most of this code into a single view file. To keep this tutorial from becoming a book, we'll just continue on knowing that this could be better.

Handing the Edit Form Submission

Now we'll create the update action. This will be similar to the create action, except it will be updating the user object:

To update the user, simply call its method with the user struct passed from the form via params. It's that simple.

After the update, we'll add a success message and send the end user back to the edit form in case they want to make more changes.

Deleting Users

Notice in our listing above that we have a delete action. Here's what it would look like:

We simply load the user using the model's method and then call the object's method. That's all there is to it.

Database Says Hello

We've shown you quite a few of the basics in getting a simple user database up and running. We hope that this has whet your appetite to see some of the power packed into the Wheels framework. There's plenty more.

Be sure to read on to some of the related chapters listed below to learn more about working with Wheels's ORM.

/config/settings.cfm
<cfscript>
	/*
		Use this file to configure your application.
		You can also use the environment specific files (e.g. /config/production/settings.cfm) to override settings set here.
		Don't forget to issue a reload request (e.g. reload=true) after making changes.
		See https://wheels.dev/3.0.0/guides/working-with-wheels/configuration-and-defaults for more info.
	*/

	/*
		You can change the "wheels.dev" value from the two functions below to set your datasource.
		You can change the the value for the "dataSourceName" to set a default datasource to be used throughout your application.
		You can also change the value for the "coreTestDataSourceName" to set your testing datasource.
		You can also uncomment the 2 "set" functions below them to set the username and password for the datasource.
	*/
	set(coreTestDataSourceName="wheels-dev");
	set(dataSourceName="wheels-dev");
	// set(dataSourceUserName="");
	// set(dataSourcePassword="");

	/*
		If you comment out the following line, Wheels will try to determine the URL rewrite capabilities automatically.
		The "URLRewriting" setting can bet set to "on", "partial" or "off".
		To run with "partial" rewriting, the "cgi.path_info" variable needs to be supported by the web server.
		To run with rewriting set to "on", you need to apply the necessary rewrite rules on the web server first.
	*/
	set(URLRewriting="On");

	// Reload your application with ?reload=true&password=wheels.dev
	set(reloadPassword="wheels-dev");

	// CLI-Appends-Here
</cfscript>
/config/settings.cfm
set(dataSourceName="back2thefuture");
// set(dataSourceUserName="marty");
// set(dataSourcePassword="mcfly");
/config/app.cfm
component {
    this.name = "WheelsApp";
    
    // Define a datasource for use in settings.cfm
    this.datasources["wheels-dev"] = {
        class: "com.mysql.cj.jdbc.Driver",
        connectionString: "yourConnectionString",
        username: "yourUsername",
        password: "yourPassword"
    };
}

id

int

auto increment

username

varchar(100)

email

varchar(255)

passwd

varchar(15)

/config/routes.cfm
mapper()
    .wildcard()
    .root(method = "get")
.end();
/config/routes.cfm
mapper()
    .resources("users")
    .wildcard()
    .root(method = "get")
.end();

users

GET

/users

Lists users

newUsers

GET

/users/new

Display a form for creating a user record

users

POST

/users

Form posts a new user record to be created

editUser

GET

/users/[id]/edit

Displays a form for editing a user record

user

PATCH

/users/[id]

Form posts an existing user record to be updated

user

DELETE

/users/[id]

Deletes a user record

/app/views/users/new.cfm
<cfoutput>

<h1>New User</h1>

#startFormTag(route="users")#
    <div>
        #textField(objectName="user", property="username", label="Username")#
    </div>

    <div>
        #textField(objectName="user", property="email", label="Email")#
    </div>

    <div>
        #passwordField(
            objectName="user",
            property="passwd",
            label="Password"
        )#
    </div>

    <div>#submitTag()#</div>
#endFormTag()#

</cfoutput>
/app/controllers/Users.cfc
component extends="Controller" {
    function config(){}

    function new() {
        user = model("user").new();
    }
}
/users/new
<h1>New User</h1>

<form action="/users" method="post">
    <div>
        <label for="user-username">
            Username
            <input id="user-username" type="text" value="" name="user&#x5b;username&#x5d;">
        </label>
    </div>

    <div>
        <label for="user-email">
            Email
            <input id="user-email" type="text" value="" name="user&#x5b;email&#x5d;">
        </label>
    </div>

    <div>
        <label for="user-passwd">
            Password
            <input id="user-passwd" type="password" value="" name="user&#x5b;passwd&#x5d;">
        </label>
    </div>

    <div><input value="Save&#x20;changes" type="submit"></div>
</form>
/app/controllers/Users.cfc
function create() {
    user = model("user").create(params.user);

    redirectTo(
        route="users",
        success="User created successfully."
    );
}
/app/controllers/Users.cfc
function index() {
    users = model("user").findAll(order="username");
}
/app/views/users/index.cfm
<cfoutput>

<h1>Users</h1>

<p>#linkTo(text="New User", route="newUser")#</p>

<table>
    <thead>
        <tr>
            <th>Username</th>
            <th>Email</th>
            <th colspan="2"></th>
        </tr>
    </thead>
    <tbody>
        <cfloop query="users">
            <tr>
                <td>
                    #EncodeForHtml(users.username)#
                </td>
                <td>
                    #EncodeForHtml(users.email)#
                </td>
                <td>
                    #linkTo(
                        text="Edit",
                        route="editUser",
                        key=users.id,
                        title="Edit #users.username#"
                    )#
                </td>
                <td>
                    #buttonTo(
                        text="Delete",
                        route="user",
                        key=users.id,
                        method="delete",
                        title="Delete #users.username#"
                    )#
                </td>
            </tr>
        </cfloop>
    </tbody>
</table>

</cfoutput>
/app/controllers/Users.cfc
function edit() {
    user = model("user").findByKey(params.key);
}
/app/views/users/edit.cfm
<cfoutput>

<h1>Edit User #EncodeForHtml(user.username)#</h1>

#startFormTag(route="user", key=user.key(), method="patch")#
    <div>
        #textField(objectName="user", property="username", label="Username")#
    </div>

    <div>
        #textField(objectName="user", property="email", label="Email")#
    </div>

    <div>
        #passwordField(
            objectName="user",
            property="passwd",
            label="Password"
        )#
    </div>

    <div>#submitTag()#</div>
#endFormTag()#

</cfoutput>
/app/views/users/edit.cfm
<h1>Edit User Homer Simpson</h1>

<form action="/users/1" method="post">
    <input type="hidden" name="_method" value="patch">

    <div>
        <input type="hidden" name="user&#x5b;id&#x5d;" value="15">
    </div>

    <div>
        <label for="user-username">
            Name
            <input
                id="user-username"
                type="text"
                value="Homer Simpson"
                name="user&#x5b;username&#x5d;">
        </label>
    </div>

    <div>
        <label for="user-email">
            Email
            <input
                id="user-email"
                type="text"
                value="[email protected]"
                name="user&#x5b;email&#x5d;">
        </label>
    </div>

    <div>
        <label for="user-passwd">
            Password
            <input
                id="user-passwd"
                type="password"
                value="donuts.mmm"
                name="user&#x5b;passwd&#x5d;">
        </label>
    </div>

    <div><input value="Save&#x20;changes" type="submit"></div>
</form>
/app/controllers/Users.cfc
function update() {
    user = model("user").findByKey(params.key);
    user.update(params.user);

    redirectTo(
        route="editUser",
        key=user.id,
        success="User updated successfully."
    );
}
/app/controllers/Users.cfc
function delete() {
    user = model("user").findByKey(params.key);
    user.delete();

    redirectTo(
        route="users",
        success="User deleted successfully."
    );
}
conventions
startFormTag()
linkTo()
linkTo()
endFormTag()
textField()
passwordField()
submitTag()
textField()
passwordField()
model()
new()
create()
redirectTo()
findAll()
linkTo()
linkTo()
update()
using the Flash
findByKey()
delete()