Skip to main content
Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.

Rediscovering My Love Of ColdFusion And CFML In 2020

By Ben Nadel on
Tags: ColdFusion

I've never been shy about how much I enjoy ColdFusion. So many wonderful things in my life have sprung from the fact that I happened to get an internship under Glen Lipka 20-years ago; and, that his company happened to be using ColdFusion 4.5. But, my relationship with ColdFusion hasn't always been rosy. We've had our good times and our bad times. And, sometimes, getting ColdFusion to do what I want has felt like a frustrating labor of love. But, when I sit back and reflect on my last year-or-so at InVision, I'm seeing now that - without question - I've fallen completely back in love with ColdFusion and CFML.

What has two thumbs and loves ColdFusion? THIS GUY! (Ben Nadel)

Much of this rekindling is due to Lucee CFML - the open-source CFML engine that we switched to at work (thanks forever to Shawn Grigson for shepherding this initiative). With Lucee CFML, it just seems like so many of the pain-points that I experienced in earlier versions of ColdFusion are gone. And, I find myself excited to wake up in the morning - thrilled to see what possibilities will unfold in the code before me!

I know that ColdFusion doesn't get a lot of respect these days. As such, I thought it would be fun to share why CFML continues to spark so much joy in my life and my heart.

DISCLAIMER 1: I am not here to tell you that ColdFusion is better than your language of choice. My only intent here it tell you why I find CFML to be extraordinary and worthy of my attention. If your particular language sparks joy in, that's awesome!

DISCLAIMER 2: I moved from Adobe ColdFusion 10 to Lucee CFML 5. Much of the pain that I felt in ColdFusion was related to an inability to upgrade to newer versions of Adobe ColdFusion. As such, the following list is not meant to denote things that are unique to Lucee CFML.

Hands-Down The Best SQL Ergonomics That I've Ever Seen

This was just as true 20-years ago as it is true today: one of the most appealing and compelling features of ColdFusion is the ease with which you can write SQL statements. Ultimately, most applications are backed by a database; which means, the easier it is to read from and write to a database, the easier it is to build most applications. And the ergonomics of the CFQuery tag are simply unmatched.

The power of the CFQuery tag springs from its tag-based nature that seamlessly mixes control-flow with SQL syntax. Take, for example, this function that queries for the contact_number records associated with a given userID:

<cfscript>

	dump( getContactNumbers( 1 ) );

	// ------------------------------------------------------------------------------- //
	// ------------------------------------------------------------------------------- //

	/**
	* I query for contact-numbers associated with the given user.
	* 
	* @userID I am the associated user.
	*/
	public query function getContactNumbers( required numeric userID ) {

		```
		<cfquery name="local.results" datasource="testing">
			SELECT
				n.id,
				n.isPrimary,
				n.isFax,
				n.phoneNumber,
				n.extension
			FROM
				contact_number n
			WHERE
				n.userID = <cfqueryparam value="#userID#" sqltype="integer" />
			ORDER BY
				n.isPrimary DESC,
				n.id ASC
			;
		</cfquery>
		```

		return( results );

	}

</cfscript>

Here, our SQL syntax is wrapped with the traditional CFQuery tag and seamlessly interwoven with our CFQueryParam tag which generates "prepared statements" and prevents SQL-injection attacks.

Of course, with one parameter, its hard to see the power of authoring SQL in this manner. Imagine if we had a function with a number of optional parameters; and, each one of those optional parameters needed to be conditionally woven into the SQL statement:

<cfscript>

	dump( getContactNumbersByFilter( userID = 1, isPrimary = true ) );

	// ------------------------------------------------------------------------------- //
	// ------------------------------------------------------------------------------- //

	/**
	* I query for contact-numbers associated with the given user.
	* 
	* @userID I am the associated user.
	* @isPrimary OPTIONAL filter for primary numbers.
	* @isFax OPTIONAL filter for fax numbers.
	* @phoneNumber OPTIONAL filter for partial number match.
	* @extension OPTIONAL filter for partial extension match.
	*/
	public query function getContactNumbersByFilter(
		required numeric userID,
		boolean isPrimary,
		boolean isFax,
		string phoneNumber,
		string extension
		) {

		```
		<cfquery name="local.results" datasource="testing">
			SELECT
				n.id,
				n.isPrimary,
				n.isFax,
				n.phoneNumber,
				n.extension
			FROM
				contact_number n
			WHERE
				n.userID = <cfqueryparam value="#userID#" sqltype="integer" />

			<cfif ! isNull( isPrimary )>
				AND
					n.isPrimary = <cfqueryparam value="#isPrimary#" sqltype="tinyint" />
			</cfif>

			<cfif ! isNull( isFax )>
				AND
					n.isFax = <cfqueryparam value="#isFax#" sqltype="tinyint" />
			</cfif>

			<cfif ! isNull( phoneNumber )>
				AND
					n.phoneNumber LIKE <cfqueryparam value="%#phoneNumber#%" sqltype="varchar" />
			</cfif>

			<cfif ! isNull( extension )>
				AND
					n.extension LIKE <cfqueryparam value="%#extension#%" sqltype="varchar" />
			</cfif>

			ORDER BY
				n.isPrimary DESC,
				n.id ASC
			;
		</cfquery>
		```

		return( results );

	}

</cfscript>

Are you beginning to see the possibilities here? Effortless SQL combined with natural and contextual control-flow. For my money, there's no amount of ORM (Object-Relation Mapper) code that feels as simple and intuitive as reading and writing SQL in the context of a CFQuery tag.

If I ever had to give up ColdFusion, this would be - undoubtedly - the feature I would miss the most.

SQL-Injection Protection As A First-Class Citizen

It's hard to believe that injection attacks are still the number one item on the OWASP Top 10 list. In the ColdFusion world, SQL-injection attacks are almost unheard of. And that's because ColdFusion offers SQL-Injection protection as a first-class language feature.

By default, the CFQuery tag will automatically escape any embedded single-quotes that are interpolated into a quoted value within your SQL statement. For example, if we tried to run the following SQL-injection attack against our own database:

<cfscript>

	// For the sake of the demo, let's try to wire-up a SQL-injection attack.
	filter = "212-555-1234' OR true -- ";

	```
	<cfquery name="results" datasource="testing">
		SELECT
			n.id,
			n.isPrimary,
			n.isFax,
			n.phoneNumber,
			n.extension
		FROM
			contact_number n
		WHERE
			n.phoneNumber = '#filter#'
		ORDER BY
			n.isPrimary DESC,
			n.id ASC
		;
	</cfquery>
	```

	dump( results );

</cfscript>

... the SQL statement that ends up getting executed is this:

SELECT
	n.id,
	n.isPrimary,
	n.isFax,
	n.phoneNumber,
	n.extension
FROM
	contact_number n
WHERE
	n.phoneNumber = '212-555-1234'' OR true -- '
ORDER BY
	n.isPrimary DESC,
	n.id ASC
;

As you can see, the single-quote in our malicious filter value was automatically escaped, rendering this particular attack ineffective.

This automatic feature is certainly a "nice to have"; but, the truth is, no one in the ColdFusion community relies on it (if they even know that it exists). And, that's because our community, as a whole, worships at the alter of the CFQueryParam tag!

The CFQueryParam tag provides a host of benefits:

  • Robust and comprehensive SQL-injection protection.
  • Automatic data-type coercion.
  • Generation of prepared statements.

So, not only does the CFQuery tag provide an elegant and ergonomic context in which to author complex yet intuitive SQL statements, its child tag, CFQueryParam, makes those SQL statement impenetrable to attacks.

Prepared SQL Statements As A First-Class Citizen

In the previous section, I mentioned that the CFQueryParam tag is used in conjunction with the CFQuery tag to make SQL statements impervious to SQL-injection attacks. The other powerful feature that the CFQueryParam tag provides is that it signals to the CFML runtime to generate a prepared statement.

A prepared statement is faster to execute than a normal SQL statement because the parsing, the query planning, and the optimization steps associated with the prepared statement are cached. This allows the database to execute the same SQL statement over and over again based on a set of passed-in parameters rather than having to evaluate the raw SQL on every request.

So, again, when we think about the vast majority of web applications being thick-clients to a database, ColdFusion prioritizes the protection and the performance of that relationship at the language level. Which is hella awesome!

Blocking By Default, Asynchronous When You Need It

By default, pretty much every operation in ColdFusion is a synchronous, blocking operation. Database queries, file I/O, HTTP requests, command-line execution, PDF generation, image process, ZIP archiving - it's all synchronous. And, for the most part, this is really nice because synchronous code is easier to reason about and less error prone.

But, synchronous code can be slow (depending on what you're doing) since it executes algorithmic steps in serial. When you need to start "fanning out" processing in an effort to cut down on execution time, it's nice to have asynchronous, non-blocking options. And for that, ColdFusion offers constructs like:

  • The CFThread tag.
  • Task threads (which I've never actually used).
  • Parallel iteration functions (like .each() and .map()).
  • The runAsync() function.
  • Asynchronous CFQuery operations (set it and forget it).
  • Scheduled tasks.
  • Email spooling.

Of these features, the one that I've historically used the most is the CFThread tag. The CFThread tag offers dead simple thread management, allowing threads to be spawned and (optionally) re-joined with minimal effort. The CFThread tag is particularly nice when you want to trigger "set it and forget it" style tasks, like analytics tracking for user-actions:

<cfscript>

	sendToSegmentAsync( 4, "Project.Delete" );

	// ------------------------------------------------------------------------------- //
	// ------------------------------------------------------------------------------- //

	/**
	* I send the given event to SegmentIO asynchronously.
	* 
	* @userID I am the user being tracked.
	* @event I am the event type.
	*/
	public void function sendToSegmentAsync(
		required numeric userID,
		required string event
		) {

		// Spawn asynchronous thread - set it and forget it - we are not waiting for this
		// thread to start or finish executing.
		thread
			name = "sendToSegmentAsyncThread"
			action = "run"
			userID = userID
			event = event
			{

			sendToSegment( userID, event );

		}

	}

	// ---
	// PRIVATE METHODS.
	// ---

	/**
	* I send the given event to SegmentIO synchronously.
	* 
	* @userID I am the user being tracked.
	* @event I am the event type.
	*/
	private void function sendToSegment(
		required numeric userID,
		required string event
		) {

		// Mock API call.
		systemOutput( "event: #event#, userID: #userID#" );

	}

</cfscript>

As you can see, we're calling the method sendToSegmentAsync(), which is spawning an asynchronous thread named sendToSegmentAsyncThread that will actually execute the blocking, synchronous task of making the API request to the external analytics service. This way, the parent page request doesn't have to wait for the API request to complete.

But, often times, we do want to wait for the asynchronous tasks to complete. We just want to fan-out processing for increased performance and then re-join the asynchronous operations for subsequent processing. For example, we might want to download a number of files in parallel and then create a ZIP archive of the contents. For that, we could leverage parallel iteration:

<cfscript>

	// Setup the remote files that we want to download to the server.
	remoteImages = [
		"https://bennadel-cdn.com/images/header/photos/vicky_ryder_8.jpg",
		"https://bennadel-cdn.com/images/header/photos/vicky_ryder_7.jpg",
		"https://bennadel-cdn.com/images/header/photos/vicky_ryder_6.jpg",
		"https://bennadel-cdn.com/images/header/photos/vicky_ryder_5.jpg",
		"https://bennadel-cdn.com/images/header/photos/vicky_ryder_4.jpg",
		"https://bennadel-cdn.com/images/header/photos/vicky_ryder_3.jpg",
		"https://bennadel-cdn.com/images/header/photos/vicky_ryder_2.jpg",
		"https://bennadel-cdn.com/images/header/photos/vicky_ryder.jpg"
	];

	scratchDirectory = "./vicky-photos";

	directoryCreate( scratchDirectory );

	try {

		// Since HTTP requests tend to be one of the slowest things we can do in an
		// applications, let's run these CFHTTP tags in parallel using the parallel
		// iteration feature.
		remoteImages.each(
			( remoteImage ) => {

				http
					method = "GET"
					url = remoteImage
					path = scratchDirectory
					file = getFileFromPath( remoteImage )
				;

			},
			// Execute collection iteration in PARALLEL.
			true
		);

		// While the parallel iteration runs things asynchronously, the overall page will
		// block and wait for the iteration to complete. As such, at this point, we can
		// be sure that the files have been downloaded to the scratch directory.
		compress( "zip", scratchDirectory, "./photos.zip" );

	} finally {

		directoryDelete( scratchDirectory, /* Recurse */ true );

	}

</cfscript>

As you can see, by passing true as an optional second argument to the .each() method, suddenly, all of those CFHTTP requests are being executed in parallel. And is being done in a way that the parent page request doesn't even need to care about because the overall iteration blocks and waits for all of the parallel requests to complete.

NOTE ON ERROR HANDLING: For parallel iteration, error handling is actually quite easy since Lucee CFML will break the iteration and bubble the error up to the parent page. The same is not true for the CFThread tag. As such, you often want to include explicit error handling inside your asynchronous code such that you can catch and log errors as needed.

I love how simple ColdFusion makes concurrency! In the wider programming world, engineers often talk about how complicated concurrent code is; but, that's just not a major pain-point that I've ever felt when writing ColdFusion. Yes, you run into some of the same race-conditions that would in any other language. But, ColdFusion makes it a lot harder for you to shoot yourself in the foot.

Beautiful Templates With Seamless HTML Integration

One of the best features of ColdFusion is that it has two different syntaxes: the Script syntax, which is what you use most of the time; and, the Tag syntax, which is absolutely perfect for rendering HTML views. The two syntax choices are functionally equivalent (for the most part); but, the tag-based syntax offers a way to effortlessly weave your ColdFusion control-flow into your HTML templates.

For example, in the following ColdFusion code, we'll use the Script syntax to gather our view-model; and then, we'll use the tag-syntax to render the HTML page response:

<cfscript>

	contacts = [
		{ id: 1, name: "Sarah", role: "Admin", avatarID: "1-av1" },
		{ id: 2, name: "Tina", role: "Admin", avatarID: "1-av2" },
		{ id: 3, name: "Arnold", role: "Manager" },
		{ id: 4, name: "Ellen", role: "Reviewer" },
		{ id: 5, name: "Steve", role: "Reviewer" }
	];

	// Add an avatar URL.
	// --
	// NOTE: I would never actually use the one-line syntax here for filter(). I'm only
	// throwing it in here to "impress" people who love short code.
	contacts
		.filter( ( contact ) => contact.keyExists( "avatarID" ) )
		.each(
			( contact ) => {

				contact.avatarUrl = "./avatars/#contact.avatarID#.jpg";

			}
		)
	;

</cfscript>
<cfoutput>
	
	<h2>
		Contacts
	</h2>

	<ul>
		<cfloop index="contact" array="#contacts#">
			<li>
				<h3>
					#contact.name#
				</h3>

				<cfif contact.keyExists( "avatarUrl" )>
					<p>
						<img src="#contact.avatarUrl#" />
					</p>
				</cfif>

				<p>
					#contact.role#
				</p>
			</li>
		</cfloop>
	</ul>

</cfoutput>

Look at how easily constructs like cfloop and cfif just slide in right alongside our HTML. Then, interpolation of output variables is as easy as adding some # signs. The ColdFusion template generation is just effortless. I love that we can slide in-and-out of the two different syntaxes as the need arises. Such powerful options!

Did I mention that you can easily include another template into an existing template by adding a cfinclude tag? Frickin' Effortless!

Script-Syntax By Default, Seamless Tag-Syntax When You Want It

In the above example, I have an explicit Script-based portion of the template and then an explicit Tag-based portion of the template. But, in recent releases of Lucee CFML, the syntax is even more flexible! We can drop down into the Tag-based syntax from within our Script-base syntax using "tag islands".

Tag Islands are one of the most exciting features in CFML because they allow us to use exactly the right tool for the job. I love the Script-based syntax as much as the next person; but, the truth is, the Tag-based syntax is better in certain circumstances.

You've already seen me use it in the earlier examples: I ran my CFQuery tags in a tag island. Tag islands are great for anything that leans towards the "markup" side of the spectrum. For example, we could use it to output an Email message body:

<cfscript>

	sendWelcomeEmail( "ben+demo@bennadel.com" );

	// ------------------------------------------------------------------------------- //
	// ------------------------------------------------------------------------------- //

	/**
	* I send the welcome email to the given user.
	* 
	* @userEmail I am the user receiving the email.
	*/
	public void function sendWelcomeEmail( required string userEmail ) {

		mail
			to = userEmail
			from = "ben@bennadel.com"
			subject = "Welcome to Foo.bar! We're so excited to have you"
			type = "html"
			{

			```
			<h1>
				Welcome to Foo.bar
			</h1>

			<p>
				We're so excited to have you join our online community. I think you find
				that we mostly talk about how awesome ColdFusion is; but, we also post a
				lot of cat memes as well :D
			</p>
			```
		}

	}

</cfscript>

As you can see, inside our Script template, we are briefly dropping down into the Tag-syntax for the HTML-based CFMail body.

We could also use tag islands in order leverage the tag-based version of built-in features like the CFQuery tag (which I've already shown) or the CFXML tag:

<cfscript>
	
	contact = {
		id: 4,
		name: "Sarah Smith",
		email: "sarah@foo.bar",
		employeeID: "e-1234-xxdf-g2"
	};

	// Generate the XML payload for our API request (old-school, baby!).
	```
	<cfxml variable="apiPayload">
		<Employee id="#encodeForXmlAttribute( contact.id )#">
			<EmployeeID>#encodeForXml( contact.employeeID )#</EmployeeID>
			<Name>#encodeForXml( contact.name )#</Name>
			<Email>#encodeForXml( contact.email )#</Email>
		</Employee>
	</cfxml>
	```

	dump( apiPayload );

</cfscript>

And, no discussion of ColdFusion tags and Tag Islands can be complete without at least a nod to the CFSaveContent tag. The CFSaveContent does just what it says: it saves content. It basically creates a buffer into which all nested output is stored. This tag is so versatile, it's hard to even pick the right use-case to show up.

One place that I've used it is in the generation of verbose RegEx (Regular Expression) patterns:

<cfscript>

	```
	<cfsavecontent variable="patternText">

		<!--- Include verbose and multi-line pattern flags. --->
		(?xm)

		<!--- First field is the ID. --->
		(\d+),

		<!--- Second field is the name. --->
		([^,]+),

		<!--- Last field is the birthday. --->
		(\d{4}-\d{2}-\d{2})

	</cfsavecontent>
	```

	// Create a Pattern Matcher for our RegEx pattern and CSV input.
	matcher = createObject( "java", "java.util.regex.Pattern" )
		.compile( patternText.trim() )
		.matcher( fileRead( "./people.csv" ) )
	;

	// Gather all of the parsed field values.
	while ( matcher.find() ) {

		dump({
			id: matcher.group( 1 ),
			name: matcher.group( 2 ),
			dob: matcher.group( 3 )
		});

	}

</cfscript>

In this example, not only are we seeing how useful the CFSaveContent tag is, we're also seeing how much leverage we can get out of the underlying Java platform (in this case, using Java's powerful Pattern Matching libraries.)

I love the ColdFusion Script-based syntax. It's clean, concise, and easy to write. But, being able to drop-down into the Tag-based syntax for certain constructs clearly leads to more manageable, more joyful code.

Positional And Named Method Arguments

Functions written in ColdFusion can be invoked using both positional arguments as well as named arguments. This is so easy and intuitive that I don't even think ColdFusion programmers are aware of just how powerful this is - and, how much it sets the ColdFusion language apart from many other languages.

Ideally, most functions that we write take so few arguments that using positional arguments is the right option. However, we sometimes have to write a function that a lot of arguments; or, we have to write functions in which some of the arguments are optional. In those cases, using named-parameters can make the code much more readable.

Take for example an INSERT function for a database gateway. Here, you can see a gateway function being invoked using both type of invocation signatures:

<cfscript>
	
	// Invoke method with positional arguments.
	createContactNumber( 2001, true, true, "212-555-9871", "" );

	// Invoke method with named arguments.
	createContactNumber(
		userID = 2002,
		isPrimary = true,
		isFax = true,
		phoneNumber = "212-555-9872",
		extension = ""
	);

	// ------------------------------------------------------------------------------- //
	// ------------------------------------------------------------------------------- //

	/**
	* I create a new Contact Number record.
	*/
	public numeric function createContactNumber(
		required numeric userID,
		required boolean isPrimary,
		required boolean isFax,
		required string phoneNumber,
		required string extension
		) {

		```
		<cfquery result="local.results" datasource="testing">
			INSERT INTO
				contact_number
			SET
				userID = <cfqueryparam value="#userID#" sqltype="integer" />,
				isPrimary = <cfqueryparam value="#isPrimary#" sqltype="tinyint" />,
				isFax = <cfqueryparam value="#isFax#" sqltype="tinyint" />,
				phoneNumber = <cfqueryparam value="#phoneNumber#" sqltype="varchar" />,
				extension = <cfqueryparam value="#extension#" sqltype="varchar" />,
				createdAt = UTC_TIMESTAMP()
			;
		</cfquery>
		```

		return( results.generatedKey );

	}

</cfscript>

As you can see, we're calling the same method twice: once with ordered arguments and once with named arguments. And, I think we can all agree, the named-argument approach makes this particular invocation much easier to understand. And, that's the beauty of this kind of flexibility - we can easily add additional clarity to our function invocation when and if it is needed.

I truly feel like this invocation functionality is an unsung hero of the ColdFusion language. People don't freak out about it because we just take it for granted.

Dynamic Invocation With Programmatic Arguments And Attributes

Dove-tailing perfectly with the previous section, another powerful feature of method invocation in ColdFusion is the fact that we can accumulate method arguments prior to method invocation by using argumentCollection. This allows us to invoke a method by passing in an Array or a Struct that contains the positional or named arguments, respectively.

Here, we're using an Array to build-up the list of positional arguments:

<cfscript>

	values = [ 1, 2, 3, 4 ];

	// Conditionally augment the arguments collection.
	if ( randRange( 0, 1 ) ) {

		values.append( 5 );

	}

	// Invoke with dynamic, ordered arguments.
	dump( sumValues( argumentCollection = values ) );

	// ------------------------------------------------------------------------------- //
	// ------------------------------------------------------------------------------- //

	/**
	* I return the sum of the given N values.
	*/
	public numeric function sumValues( /* variadic signature */ ) {

		// NOTE: I wouldn't normally use a .reduce() function for such a simple
		// operation. I'm only including it here to ooh-and-ahh those people who believe
		// that every single loop ever should be "functional programming" esque.
		var total = arguments.reduce(
			( runningTotal, value ) => {

				return( runningTotal + value );

			},
			0
		);

		return( total );

	}

</cfscript>

And here, we're doing something similar, except with a Struct for named arguments:

<cfscript>

	args = {
		person: "Sarah",
		message: "I hope that all is well"
	};

	if ( randRange( 0, 1 ) ) {

		args.greeting = "Hello";

	} else {

		args.greeting = "Good morning";
		args.punctuation = "!";

	}

	// Invoke with dynamic, named arguments.
	dump( prepareGreeting( argumentCollection = args ) );
	
	// ------------------------------------------------------------------------------- //
	// ------------------------------------------------------------------------------- //

	/**
	* I return the prepared greeting.
	* 
	* @greeting I am the greeting salutation.
	* @person I am the person being greeted.
	* @message I am the main message being communicated.
	* @punctuation I am the closing punctuation for the message.
	*/
	public string function prepareGreeting(
		required string greeting,
		required string person,
		required string message,
		string punctuation = "."
		) {

		return( "#greeting# #person#, #message##punctuation#" );

	}

</cfscript>

The above snippets demonstrate the power of dynamic method invocation; but, ColdFusion also allows the same level of dynamic execution with Tags and attributeCollection. The idea is exactly the same: build up the attribute collection programmatically; then, invoke the tag with the given collection:

<cfscript>

	mailArgs = {
		to: "ben+me@bennadel.com",
		from: "ben@bennadel.com",
		subject: "ColdFusion is the bee's knees!",
		type: "html"
	};

	// Invoke with dynamic attributes.
	mail attributeCollection = mailArgs {
		```
		<p>
			Hey Ben,
		</p>

		<p>
			How freaking cool is the `argumentCollection` and `attributeCollection`
			stuff? Like, it's so freaking cool!
		</p>
		```
	}

</cfscript>

As you can see, the CFMail tag is being invoked with a dynamic set of attributes. How deliciously awesome!

Automatic Scope Population

In ColdFusion, a "Scope" is just a specialized Struct that gets populated automatically by the runtime. This allows you to easily access data without having to worry about low-level parsing or incoming request structure. For example, here are some of the Scopes that I use every day:

  • URL - The URL scope is automatically populated with the decoded URL search parameters.

  • FORM - The FORM scope is automatically populated with the form-fields in application/x-www-form-urlencoded and multipart/form-data requests. Furthermore, ColdFusion automatically places file-uploads into a secure, temporary directory where they can be safely inspected by your application.

  • CGI - The CGI scope is automatically populated with values that are passed-through from the web-server (things like cgi.https, cgi.server_name, cgi.http_referer, cgi.remote_addr, and cgi.http_user_agent are particularly helpful). One of the luxurious feature of the cgi scope is that it will return an empty string for null references. Since every web-server passes-through different values, this makes it safe and easy for the cgi scope to be used in a variety of contexts.

  • COOKIE - The COOKIE scope is automatically populated with the request cookies. But, more than that, if you add a new value to the cookie scope on the server, ColdFusion will automatically pass-back a Set-Cookie header in the HTTP response. So crazy easy!

  • SERVER - The SERVER scope is automatically populated with the server's environment variables (in server.system.environment), making it effortless to pull-in environment-aware configuration.

ColdFusion has quite a few more Scopes; however, the ones above are the ones that are automatically populated in the request. This feature really removes any friction that you may have encountered when trying to consume incoming request data.

CFDump + CFAbort - The King Of Debugging Tools

One of the first thing that any ColdFusion developer learns is how to dump data to the screen using the CFDump and CFAbort tags. The CFDump tag - and its script-based dump() equivalent - can output any type of data in a rich, easy-to-read, interactive format. It's truly the king of debugging tools. I literally use this CFML feature every day:

<cfscript>

	dump( "A man, a plan, a canal: Panama" );

	dump({
		id: 4,
		name: "Ben Nadel"
	});

	dump([ "a", "b", "c" ]);

	dump(
		queryExecute(
			sql = "SELECT * FROM contact_number LIMIT 1 ;",
			options = {
				datasource: "testing"
			}
		)
	);
	
</cfscript>

Which gives us the page output:

CDump output in ColdFusion CFML.

I literally cannot overstate how awesome CFDump and dump() are. I must use them a dozen times a day. And, if you throw a CFAbort / abort after it, ColdFusion will halt the page processing and output the current buffer to the page, regardless of what you are doing. It's so powerful!

NOTE: You can even tell CFDump to output to the "console" (System.out), making it easy to use even in cases where the processing is happening asynchronously (such as within a CFThread tag) and cannot be easily aborted.

Security As A First-Class Citizen

I've already talked about the CFQueryParam tag and how it effortlessly protects your ColdFusion application from SQL-injection attacks. But, that's not the only security-minded feature in CFML. In fact, ColdFusion comes with loads of security functions baked right in:

  • encodeForCSS()
  • encodeForHTML()
  • encodeForHTMLAttribute()
  • encodeForJavaScript()
  • encodeForURL()
  • encodeForXML()
  • encodeForXMLAttribute()
  • encodeForXPath()
  • canonicalize()

These help to prevent things like persisted and reflected Cross-Site Scripting (XSS) attacks and other inject-relation vulnerabilities.

The other big player in the security feature-set is the CFParam tag. The CFParam tag plays three major roles:

  • Validating expected data-types.
  • Providing default values.
  • Documenting the expected key.

I use the CFParam all the time, primarily in the Controller and View layers of my ColdFusion application. Here's a pseudo-example of a Controller method in a Framework-One (FW/1) application:

<cfscript>

	// ....

	/**
	* I create a project.
	* 
	* @rc I am the request context.
	*/
	public void function create( required struct rc ) {

		// Document and validate expected and optional inputs.
		param name = "rc.userID" type = "numeric";
		param name = "rc.projectID" type = "numeric";
		param name = "rc.isFavorite" type = "boolean" default = false;
		param name = "rc.makePublic" type = "boolean" default = false;

		rc.apiResponse = projectService.createProject(
			userID = rc.userID,
			projectID = rc.projectID,
			isFavorite = rc.isFavorite,
			isPublic = rc.makePublic
		);

	}

	// ....

</cfscript>

As you can see, the CFParam tag is being used to document which variables this Controller method is expecting; it's validating the input data-types; and, it's providing default values for the optional inputs. If any of the validations fail, the CFParam tag will automatically throw an error, making the rest of your code confident that the right inputs have been provided.

I use the CFParam tag all the time. It's hard to even understand how helpful it is in creating readable code until you come across code that doesn't use it.

List Functions

The list functions in ColdFusion are such an innocent little feature that you barely even realize how seductive they are until you move to another language that doesn't have lists. And then, you find yourself wanting them all the time! In ColdFusion, every String is implicitly a List as well. Because the list functions are nothing more than a way to parse and manipulate strings based on the concept of delimiters.

For example, if you think about an email address as a delimited list, getting the domain is simple:

<cfscript>

	email = "ben@foo.bar";

	// If we treat the email as a limited list with "@" as the delimiter, the domain
	// portion is just the last item in that list.
	dump( email.listLast( "@" ) );	

</cfscript>

As you can see, we're treating the email address as a list in which the "@" sign is the delimiter.

It turns out, when you start training your brain to see Strings as potential Lists, you can start to use these list functions all over the place for quick manipulations. Lists are another one of those unsung heroes of the language.

1-Based Arrays Lead To Simplified Truthy / Falsy Conditionals

This is a feature of CFML that tends to trip people up because ColdFusion is one of the rare languages that uses 1 as the start of Arrays and other types of offsets. Meaning, Arrays start at index 1, not index 0 like many other languages. And, as someone who programs in ColdFusion and JavaScript every single day, I can honestly say that 1-Based Arrays make life easier.

Perhaps the best feature of a 1-Based offset system is the fact that 0 - not -1 - becomes the "failure" result. This allows the failure result to be used in a conditional control-flow statement as a Falsy with better readability.

<cfscript>

	roleID = 4;
	privlegedRoles = [ 1, 4 ];

	// Check to see if the user's role is allowed to access this part of the app.
	if ( ! privlegedRoles.find( roleID ) ) {

		throw( type = "Forbidden" );

	}

	echo( "You're in! Welcome...." );

</cfscript>

Here, we're using the .find() method to see if the given roleID exists in the list of permitted roles. The .find() method returns the index of the found value; or, 0 if the value cannot be found. And, since 0 is a "falsy" value, it means that a find-failure will leads to a failed if conditional check without any additional value comparison.

I said that I'm not here in this post to compare ColdFusion to any other languages; but, for this particular instance, I think a language comparison might be helpful to illustrate the point. A similar approach in JavaScript might look like this:

var roleID = 4;
var privlegedRoles = [ 1, 4 ];

// Check to see if the user's role is allowed to access this part of the app.
if ( privlegedRoles.indexOf( roleID ) === -1 ) {

	throw( new Error( "Forbidden" ) );

}

console.log( "You're in! Welcome...." );

Now, you can see the readability of the two approaches side-by-side:

  • CFML: if ( ! privlegedRoles.find( roleID ) )

  • JS: if ( privlegedRoles.indexOf( roleID ) === -1 )

As I said, I program in ColdFusion and JavaScript every single day. And, for me, the first one is always easier to read and to reason about.

ASIDE: There are many methods in both ColdFusion and JavaScript that use method names that offer different levels of readability and return Booleans instead of offsets. For example, we could have just as easily used something like .includes() in JavaScript to make the condition more readable. But, this was just an example context. Don't over-think it.

I've actually been coding in JavaScript longer than I've been coding in ColdFusion. And still, I find ColdFusion's 1-based array and offset paradigm easier to think about and to use. In fact, the only time I ever think about it is when I have to dip down into the Java layer, which is 0-based, and perform some translation. But, those moments are few and far between.

Built-In Scheduled Task Management (aka Cron Jobs)

The ColdFusion application server comes with built-in cron-job functionality in form of "Scheduled Tasks". These are URLs that are configured to be pinged on a consistent basis such as "Every 5 minutes", or "every morning at 10am". Scheduled tasks make it easy to execute application code in the background when it's not tied to any particular user-request.

Throwing Errors

I've done quite a bit of research on error handling in web applications. And, everything that I've found points me in the direction of throwing errors as means to create readable, maintainable code. ColdFusion applications throw errors, both implicitly and explicitly; but, what I wanted to talk about here is just how easy it is to throw an error with a lot of contextual information.

Error objects are super helpful for debugging; however, they are only as helpful as the information that they contain. In ColdFusion, the CFThrow tag, and its throw() script-based equivalent, allow us to define the following data-points:

  • type
  • message
  • detail
  • extendedInfo
  • errorCode

With these fields, we can create rich error objects without having the hassle of creating custom Error classes. Essentially, we can both throw and catch based on the type field; and then, push all kinds of error-specific debugging information into the additional fields.

Throw errors, along with the flexibility of the CFThrow tag, really make debugging ColdFusion applications quite enjoyable.

Built On Top Of Java

As I alluded to in my section on Tag Islands, ColdFusion is built on top of Java. This means that when ColdFusion doesn't have something built-in natively, we can easily leverage the massive open-source Java community to meet our requirements. AWS clients, MongoDB drivers, Redis drivers, AntiSammy sanitization, MarkDown parsers, SAML Authentication libraries, etc. If it has been written in Java, it can be loaded as a JAR file into a ColdFusion application.

On its own, the ColdFusion community is smaller and more cozy; but, by extension of the underlying Java runtime, the ColdFusion community becomes massive!

Automatic Memory Management Lets You Focus On Business Problems

ColdFusion has always been a language with automatic memory management (via Garbage Collection). Garbage Collection is not perfect; and it can lead to "stop the world" events that put a temporary halt on your server. But, the benefit of this trade-off is that your code is focused entirely on business problems. No pointers, no dereferencing - just delivering user-value.

This also makes it easier for developers of all levels to jump into a project and start contributing. At work, we have engineers who have never used ColdFusion before jump in and start making changes to the application. And, they can do that because the barrier to entry is low (especially in a brown-field application that has already laid-down all the groundwork).

It Works With Or Without A Framework

There are a variety of ColdFusion frameworks that empower engineers to build large-scale, robust applications. But, out of the box, ColdFusion "just works". Meaning, you can spin up a ColdFusion application server, add a few .cfm (ColdFusion Markup) files, and you have a working site. In fact, my blog (www.bennadel.com) - this website - doesn't use a framework at all; it's literally just a series of CFSwitch and CFInclude tags that pull in different ColdFusion templates.

What this means is that your ColdFusion application's complexity can evolve as its needs evolve - you don't need to "boil the ocean" just to get a "Hello World" example working.

That said, the built-in ColdFusion Application Framework - Application.cfc - is a wonderful mixture of simplicity and flexibility. It allows you to tap into application life-cycle events; and will automatically do things like single-thread the application boot-up process.

Functions, Components, and Scripts - OH MY!

There's really very little in the ColdFusion world that I would consider to be idiomatic ColdFusion code. And that's because ColdFusion is super flexible. Instead of forcing you to fit all use-cases into a single paradigm, ColdFusion gives you the freedom to choose the right tool for the right job.

  • You want to use Tags? No problem!

  • You want to use Scripts? No problem!

  • You like Object-Oriented Programming (OOP)? No problem!

  • You feel more comfortable writing procedural scripts? No problem!

  • You love writing SQL? No problem!

  • You feel more productive with an ORM? No problem!

  • You want the power of a framework? No problem!

  • You want the simplicity of vanilla cfm pages? No problem!

  • You want out-of-the-box session management? No problem!

  • You prefer to keep your application servers stateless? No problem!

  • You want to use an in-memory cache? No problem!

  • You want to use a scalable, distributed cache? No problem!

  • You love declarative functional programming? No problem!

  • You prefer imperative code? No problem!

The beautiful thing about ColdFusion is that it doesn't get in your way. It just gives you loads of options; and, then let's you rock out however you feel more comfortable and productive. ColdFusion allows you to focus on delivering value - it doesn't really care how you get there because, at the end of the day, the users won't care you got there.

Honorable Mention: The ColdFusion Query Object

My first call-out in this post was the best-in-breed SQL ergonomics that ColdFusion provides. But, I didn't really talk about what comes back from our database access requests: the ColdFusion Query object.

ASIDE: In Lucee CFML, you can opt-in to having your CFQuery tag return the record-set as an Array-of-Structs if you want.

In some ways, the ColdFusion Query object is the keystone of the ColdFusion zeitgeist. It's one of the first complex objects that you ever work with; and, its elegance and flexibility make it a complete joy to consume. As ColdFusion developers, we work with the Query object so often that we likely aren't even cognizant of how beautiful it is - we just take it for granted.

Some things that I love about the ColdFusion Query object:

  • It automatically assumes column reference are for the the first row when not in a loop-context.
  • It automatically assumes the correct row reference when in a loop-context.
  • It returns null values as empty-strings.
  • It returns out-of-bound values as empty-strings.
  • It can be used in a query-loop.
  • It can be implicitly cast to an Array-of-Structs with a for-in loop.
  • It has built-in functions for generating column value-lists.
  • It has built-in sort functions (easily allowing us to distribute ORDER BY functionality to the horizontally-scaled application tier).

And, of course, there's the ColdFusion Query-of-Queries (QoQ). The truth is, the CFML language has evolved to the point where Query-of-Query functionality isn't really needed. But, for back in the day, Query-of-Queries was a killer feature. For those of you who don't know what that is, it's a way to execute SQL against an in-memory Query object. And, it was massively powerful!

All Your Skills Are Highly Transferable

Despite its reputation, ColdFusion is not some whacky, out-there programming language. In fact, if you omit some of the optional syntax, it's getting harder and harder to tell whether you're looking at ColdFusion code or JavaScript code or TypeScript code. The skills you learn as a ColdFusion developer are highly transferable to other programming languages; because the semantics are familiar and the problem space is familiar. As such, every moment that you spend getting better at ColdFusion is an implicit investment in your career as a web developer in general.

Special Thanks to Michael Born

I just wanted to give a special shout-out to Michael Born who, in part, inspired this post with his post on why he doesn't care that ColdFusion is unpopular. Our community is vibrant and beautiful and passionate about what we do. And I love being part of it.

Wow! What a List!

As I mapped this post out in my head, I knew there was a lot of exciting ColdFusion functionality that I wanted to cover! And, these are just the high-level points - I didn't even get into the dynamic nature of the runtime, the meta-programming opportunities, the Virtual File Systems, function memoization, query caching, language extensions, distributed caches, session management, distributed session management, Object-Relation Mapping (ORM), custom tags, CFDocument, CFPDF, CFZip, CFSpreadSheet, CFExecute, image manipulation, closures, fat-arrow syntax, Cyptography, HTML parsing, XPath. And, a whole host of other things are that aren't even popping-to-mind but contribute to making ColdFusion a wonderful language to fall in love with.

Did I miss anything? What about ColdFusion sparks joy in your life?



Reader Comments

@Brad,

Thank you :D I was considering mentioning CommandBox, which has personally been a huge boon to my local-development efforts. But, I decided to keep it more language-focused. But, CommandBox should get an honorable mention!

Reply to this Comment

So true. Having worked with CFML for the past year I was recently "forced" to switch to PHP.

And I'm missing everything you mention here! Particularly the cf query and named arguments!

Reply to this Comment

@Michael,

I'm sorry you're missing it; but, I'm glad that some of the things here are striking a chord with people! Especially with people, like yourself, who probably have boarder experience than I do.

Reply to this Comment

Do people have recommendations for reporting when moving from Adobe CF to Lucee?
A lot of our projects have a lot of CFRs and this has always been a hurdle to moving.

Reply to this Comment

Wow, just realised I have been doing CFML for 20 years too!

I also was taken by CF tags within HTML - it felt so natural. And yes, CFQUERY was the big one for me as well. No messing with installing and configuring connection strings.

Another great post a Ben!
I have lost count of how many searches have lead me to your website and have not only giving me an answer, but working code ai can take with me ;-)

Reply to this Comment

Ben, this is a really awesome blog post. Your blog is of such a valueable content for our community! I've never posted a comment here, so now the time has come to do so: Your blog doesn't only show some important "how to's" or ideas and solutions, it definitely reflects the passion, excitement, love and joy for the cfml language and life. From your photo carroussel to the way you choose your words. Thank you for that! Love to read it! Best read in the morning accompained by a hot Cappuccino :D

Reply to this Comment

Hi Ben. Great article - you made my day! I've been using CF since v6.1. I switched to Railo/Lucee in 2015 somewhere and never looked back. This is a great list of CFML functionality with all our old pals ... cfquery, cfqueryparam, cfmail, Application.cfc. And then cfdump to the rescue. Thanks again for taking the time to put this gem together.

Reply to this Comment

cfdump + cfabort FTW!!! I have yet to find anything with the same detail level and ease of use.

Reply to this Comment

Ben - this post is masterful! You've succeeded in getting me excited again too. I've been loving all your Lucee posts, especially as I migrate my app from ACF to Lucee. To see all the value adds listed in one place...the things we tend to take for granted put in print so we can re-appreciate them...a-m-a-zing! As always, I appreciate you! We all do!

Reply to this Comment

@Alan,

What do you mean when you say "reporting"? I've never really done a lot of reporting in ColdFusion outside of generating CSV files and PDFs. Are you talking about things like CrystalReports?

Reply to this Comment

@AJ,

CFQuery is just great. Nary a day goes by where I don't write some SQL inside a query tag. It's been a powerful weapon since day-1; and, it's just as important today.

Reply to this Comment

@Andreas,

Thank you very much for the kind words; and, I'm thrilled that this post in particular connected with you since it's all about how much I love work with this language and with this community. Here's to many more great years!

Reply to this Comment

@Stefan, @Jean,

It's crazy how useful CFDump is. You'd think, after all these years, I'd be using some sort of slick interactive step-debugger; or, at the very least outputting stuff to the console (though, to be fair, I do use the console a lot with systemOutput()). But, I'm still reaching for CFDump every day! Literally :D

Like 90% of my problems are solved by just outputting the values I have. So simple, so powerful.

Reply to this Comment

@Chris,

Thank you very much, good sir -- I feel like we've been going on this journey together, and it's nice to stop and celebrate the moment every now and then. Let's keep the party going !! Woot woot!

Reply to this Comment

@Ben

Fantastic article!

I helped my last company to move the code from ACF to Lucee. It was great experience. During the migration I have realized the few issues with CFML which are not related with the language.

How to write a code is very important. Unfortunately people are not using SOLID principle effectively. Adobe support is not great when it comes to the language and also has very expensive licensing.

I am glad Lucee is providing better alternative to ACF. Ortus is helping with their great opensource tools. Your articles always helps.

Thanks...

Reply to this Comment

@Vikrant,

It's a great point, re: SOLID principles. It turns out, you can write bad code in any language :D I am always reminded of this whenever people think that their language is causing them to make bad architectural decisions. It's like that quote about Microservices and Monoliths (paraphrasing):

If you couldn't design a monolith well, what makes you think you could design microservices well?

Sometimes, I wonder if it's a byproduct of the industry - we working in a field where the average job-span is like 2-years. If the industry had more "stick-to-it", I wonder if we'd be less volatile about the tech we use? Who knows.

Reply to this Comment

@Ben,

Yes something like Crystal Reports or Jasper Reports.

We use a lot of CFRs (Reports built with Adobe Coldfusion Report Builder) so everytime look to move this is seems to be our biggest barrier. I was just curious if anyone had to deal with this if they moved from ACF to Lucee, and what they found worked best.

Reply to this Comment

Thank's Ben for your reply and help. There's is definitely some good information there to consider. Love the blog!

Reply to this Comment

@Alan,

Awesome! Wish I could give you some more hands-on advice; but, I will say that the people in the Lucee Dev Forum are very helpful.

Reply to this Comment

How are you putting cfml inside cfscript? I tried the triple backticks you were doing, but that doesn't work in cf11, haven't tried 2018 which we're in the process of switching to. I've written coldfusion for decades and I have never seen it before. One dev I knew even went so far as to write a query handler that uses cfcontent so he could write prettier queries inside cfscript, lol.

Reply to this Comment

Very refreshing to read such a positive post on ColdFusion in 2020. You addressed so many good aspects of CFML. All that weight lifting hasn't turned you into a meat head, Ben! LOL! Adobe should hire you at the official avagenlist. Good work.

Reply to this Comment

@Jason,

Yeah, it's Lucee CFML only at this time; and, for that matter, I think it's only in a very recent release of Lucee. At work, I'm still on 5.2.x and can't use Tag Islands :( But, hopefully we'll be able to upgrade soon and I'll be able to start using them in more than just my R&D.

Reply to this Comment

@Gary,

Ha ha ha, thanks :D I feel strongly about some things, and one of them happens to be awesome luxurious the CFML language is :D

Reply to this Comment

The ONLY time tag syntax is 'better' is in view pages.

'Tag islands' are the dumbest feature added to CFML since cfpod.

Learn how to use script syntax...or don't. Mixing them like that is a crime against humanity.

Reply to this Comment

@Ben,

Agree to disagree.

I know how to use cfscript, this is the worst of both worlds.

This is some of the most difficult code to read and encourages people to not learn proper syntax.

Reply to this Comment

@Scott, @Ben,

It's not tidy, can be confusing, and doesn't seem "clean" to switch back and forth between tags and script. My argument for doing it (as much as I dislike it) is cfscript v1.0 is horrible when you want to use functions - it's illogical and inconsistent. Tags are so much easier to code and comprehend for using powerful CF features.
However, variable setting and manipulation and doing layers of if/else logic is easier and looks nicer using cfscript. So I understand the best of both argument too.
If cfscript v2.0 resolves all issues then I guess more people will stick to cfscript throughout, although from what I've seen there will be a learning curve to v2.0 as the syntax is completely different in CF 2020. (Not that I can afford to upgrade for a few years!) The documentation better be thorough and clear.

Reply to this Comment

@Scott, @Gary,

I think it's just a matter of personal preference, not a matter of understanding the syntax. I understand how to write queries with queryExecute(); but, me and my teammates still prefer writing tag-based Gateway components simply because we all find the readability and maintainability of CFQuery to be nicer. But, I'm not going to posit that everyone feels that way; I can only say that my team prefers tag-based queries (and we're on a version of Lucee that doesn't yet support Tag Islands).

The downside of writing tag-base Gateway components is that the Function and Argument and <cfset> tags are all so much more verbose. But, we're willing to incur that additional noise for the benefit of the CFquery + CFQueryParam integration. That's how much we enjoy those ergonomics.

To each their own. And, certainly, I don't think anyone here is suggesting that one should jump back-and-forth between Script and Tags for no reason. The point with the tag islands is that you can choose the right tool for the right job.

Reply to this Comment

@Scott - we'll have to agree to disagree as well. There are certainly times where a tag is the better choice.

As an example, we do a tremendous amount of work with (much) older databases and for some of the SQL statements it is required to use both single quotes and double quotes sprinkled withing the sql, which cannot be escaped due to limitations of the JDBC driver. To use queryExecute in those situations means a horrific amount of string concatenation to get an executable query.

The choices are then: switch completely to a tag based component for these; cfinclude a tag based function into a script based cfc; try to use savecontent for the sql string (but that has it's own horrific issues with echo/writeoutput and quotes); or we could use the tag island in our otherwise script cfcs and achieve a clean result. Bulk query inserts are another one that can sometimes get a bit ugly with queryExecute.

Purity for purity's sake seems silly and many of these "never do this..." mantras are a little bit more religious than they need to be IMO.

Reply to this Comment

I really like to use cfscript all the way if it is possible, but tags are very useful in the view.

Since Lucee is supporting the tags in the script, we have advantage of mixing them.

I don't mind to use cfquery inside the cfscript. It is is east to read, but then it could be disaster if we are not following clean code strategies or oops principle. How to write a code is always tricky for developers.

Powerful feature like this comes with big responsibility. I don't mind have some strict rules.

Reply to this Comment

@Vikrant,

Powerful feature like this comes with big responsibility.

Exactly. I don't think anyone here wants to see arbitrary mixing of code. I'm only intending to show the use-cases that I think make sense. The right tool for the right job.

Reply to this Comment

Nice to see this post Ben. I've been developing with Angular on the front end, and both PHP and Coldfusion (we're on CF 2016) on the back end. Hands down, I still love working with CF and find it far more capable and intuitive than PHP.

The only thing I would love to see is Adobe actually supporting their products. Long ago switched from Dreamweaver to IntelliJ, because their support for CFML is so much stronger, and because integration with angular projects is seamless.

Reply to this Comment

@Ben,

You know you can build the query relatively easy in cfscript and concatenate it together at the same time building your params in a struct in each if block. I find it's "almost" as readable as the tag based format.

sQry = "SELECT
		n.id,
		n.isPrimary,
		n.isFax,
		n.phoneNumber,
		n.extension
	FROM
		contact_number n
	WHERE
		n.userID = :userID ";
stParams = {"userID":{cfsqltype="cf_sql_integer",value:"#userID#"}};

if (! isNull( isPrimary )){
	sQry &= " AND
			n.isPrimary = :isPrimary ";
	structAppend(stParams,{"isPrimary":{cfsqltype="cf_sql_tinyint",value:"#isPrimary#"}});
}

	// ....
sQry &=" 
	ORDER BY
		n.isPrimary DESC,
		n.id ASC; ";

queryExecute(sQry, stParams, {datasource = "testing"});

But like you said to each his own.. whatever works for you and your team :)

Cheers,

Gary

Reply to this Comment

@Charles,

I'm happy to hear that someone with ongoing experience with both PHP and ColdFusion is still finding ColdFusion so enjoyable. I've only dabbled with PHP back in the day (and had to read some code in more recent years). I always found CF more enjoyable; but, my experience is so limited.

Reply to this Comment

@Gary,

Not sure what happened to the formatting in your comment there (my guess is a funky back-tick somewhere); but I think I get your point. That's an interesting approach. I'd have to try it out a bit before I can get a sense of how scalable / flexible it would be.

I'll see if I can fix the formatting.

Reply to this Comment

Have to say I'm back in this world as of a year or so again also...

I'm just wish there were more 'provider' libraries available, so I'm pumping out a bunch of framework agnostic providers for all sorts of social and other integrations... once we are fully live I'll release them also.

That said been using Abram Adams dao a lot lately to replace queries, but want to to go one step further than Norm/Linq and use descriptive queries and autowire based on configuration (for backend and front end validation with pure javascript = any framework)

so great to see you back, always loved your posts, you started later than most, but took off at a rapid rate and compiled this amazing blog full of articles I've used over and over and over...

Reply to this Comment

@Dawesi,

The provider libraries sounds really interesting. Looking forward to seeing what you put out there.

Regarding things like Linq, I don't have too much experience with the .Net side of things; but, I know that on the Modernize or Die podcast, they are always referring to a ColdBox modules for writing queries with a "fluent" API. I think the module in question is qb (which I assume stands for "Query Builder"):

https://qb.ortusbooks.com

... not sure if you can get any inspiration from this, or if it even overlaps with what you're talking about. But, it popped into my head, so I am sharing :D

Reply to this Comment

Hi Ben,

thanks for the post, great stuff. You summarize the reasons why we have created and improved Lucee in the first place. The joy of programming. I can tell you the exact reason why we introduced the tag notation in Lucee.
The reason is mostly because there are many people out there programming in CFML which are so used to tags, that they don't want to switch to CFScript. An even better example is the CFMail tag. Here inside the CFMail tag you are already outputting variables etc. So it is a little awkward to use writeOutput() or echo() to generate the output. Of course you could write that part in CFTags, but with the upcoming .cfs file extension it was necessary as well to introduce some way to use tags in script.
So with this way of coding we were able to convince way more Taggers to convert using CFScript in their code and fall for it :)

If I look at other stupid things that have been introduced in CFML, this definitely doesn't make my top 20 of bad things introduced. I hope we listen to our community and improve the language continuously.

I also have to say I am so happy that I can program in Lucee every day!

Thanks again Ben!

Reply to this Comment

@Gert,

The CFMail tag is a great example of where tag-based output is helpful. I think it's a great feature, when used in moderation.

What is this .cfs file-extension you mention? Is that going to be a Lucee CFML thing?

Reply to this Comment

@Ben,

It is a purely script file. We have .cfc where you can either start with cfcomponent or simply write cfscript. And .cfm where you can write tags and move to script with cfscript.
.cfs files will be files purely for cfscript. In there, if you wanted to use tags for whatever reason, you would need to use the triple backticks.

Gert

Reply to this Comment

@Gert,

Ahhhh, I get it! Very cool :D The biggest challenge will be getting the IDE's and the GitHub Gists to update their color coding ;)

Reply to this Comment

@Gary,

Ah! I am doing something similar using queryexecute, but I will surely give you way a look! Tag Islands are great... but it breaks my eyes. haha

Thanks for sharing!
Fred

Reply to this Comment

@Vikrant,

Yeah, SublimeText has that as well. But, the languages sometimes have use-cases that the plugins don't [yet] account for. I wish I knew up to edit a markup-scheme; maybe I could fix some of those bugs.

Reply to this Comment

Lucee has been an awesome alternative to ACF and has a lot of promise going forward. Just one thing to note "..love declarative functional programming? No problem" CF is not functional like F# or Haskel but the enhancements are nice nonetheless!

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Blog
Live in the Now
Oops!
NEW: Some basic markdown formatting is now supported: bold, italic, blockquotes, lists, fenced code-blocks. Read more about markdown syntax »
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.