Blog

Eric Peterson

June 30, 2020

Spread the word


Share your thoughts

CFCasts launched at the beginning of this month on the tails of Into the Box 2020. We wanted a way to share all of the great content from Into the Box with those that attended with a great experience that matched the content. Additionally, we wanted to offer our training and experience to the CFML community going forward. Over the past month, we've added over 10 hours of free videos to CFCasts. More will be coming every month. Yesterday, subscriptions were made available allowing access to even more content, including the ColdBox Master Class. New content will be coming out every week. It truly is the premier learning resource for modern CFML developers.

Being a new platform from Ortus, I wanted to take some time to dive into the tech stack we are using. Many people ask us how we would build a modern CFML site. Well, here's at least one answer:

Our Stack

ColdBox 5

The heart and soul of CFCasts is ColdBox 5. Modules and interceptors, in particular, make the rest of the stack possible. The built-in flash scope powers the notifications around the site. CFCasts is built off of the quick-tailwind-inertia template which bundles a bunch of awesome modules wired together out of the box. It's a great starting point for creating an app using this stack. We're excited to upgrade to ColdBox 6 soon and specifically to take advantage of its asynchronous scheduling features.

Lucee 5

We chose Lucee 5 as our CFML engine. Lucee's extensibility allows us to plug in caching solutions like Couchbase at the engine level. Additionally, we use CFConfig to configure CFCasts on all levels — development, staging, and production.

MySQL 5 and CommandBox Migrations

MySQL 5 has been around for a while now. While not the latest version, it is very stable and something we are very familiar with. It is not only a rock-solid database, but it is also powering our upcoming search functionality through full text search matching. We configure our database using commandbox-migrations and qb's SchemaBuilder. Here's an example of one of our migrations:

// resources/database/migrations/2020_05_18_073000_create_watches_table.cfc
component {

    function up( schema, query ) {
        schema.create( "watches", ( table ) => {
            table.increments( "id" );
            table.unsignedInteger( "userId" )
                .references( "id" )
                .onTable( "users" )
                .onDelete( "CASCADE" );
            table.unsignedInteger( "videoId" )
                .references( "id" )
                .onTable( "videos" )
                .onDelete( "CASCADE" );
            table.datetime( "createdDate" ).withCurrent();
            table.unique( [ "userId", "videoId" ] );
        } );
    }

    function down( schema, query ) {
        schema.drop( "watches" );
    }

}

Migrations allow us to quickly bring up each of our development environments and share those changes between each of our developers. Those same migrations are run in our staging and production environments from our secure GitLab CI pipelines.

Quick 3

Our persistence layer is written using Quick. It allows us to have a clean, readable, object-oriented API to interact with our database — lightning-fast and written entirely in CFML. Here's a great example of the type of code Quick allows us to write:

// handlers/RedeemedCoupons.cfc
component secured {

	property name="couponService" inject="quickService:Coupon";

	function create( event, rc, prc ) {
		var coupon = couponService
			.redeemable()
			.findOrFail( rc.couponId );

		coupon.redeem( auth().user() );

		flash.put( "message", {
			"type": "success",
			"title": "Congratulations",
			"body": coupon.generateRedemptionMessage()
		} );

		relocate( coupon.getRedemptionRelocatePath() );
	}

}

Quick has been amazing for our team's productivity and improving the readability of the code we write.

cbauth and cbguard

We use cbauth and cbguard to handle authentication and authorization for CFCasts. With it, we can easily require users to be logged in for different handlers or actions or even have certain permissions. The best feature is that our authentication system is the same as our other modern apps so we don't have to learn or relearn a new authentication system when switching between apps. We look forward to merging cbguard and cbsecurity and moving to cbsecurity in the future.

cbMailServices, Postmark, and MJML

CFCasts sends a few emails — password resets, coupons, subscription notifications, etc. We use a few tools to make this straightforward, responsive, and beautiful. cbMailServices is a protocol-based module for ColdBox. Using ColdBox environments we set up a different protocol between testing, development, staging, and production. Here's what that looks like:

component {
	function configure() {
		mailsettings = {
			"tokenMarker" : "@",
			"protocol"    : {
				"class"      : "cbmailservices.models.protocols.PostmarkProtocol",
				"properties" : { "APIKey" : getSystemSetting( "POSTMARK_API_KEY", "" ) }
			}
		};
	}

	function development() {
		mailsettings = {
			"tokenMarker" : "@",
			"protocol"    : {
				"class"      : "cbmailservices.models.protocols.FileProtocol",
				"properties" : {
					"filePath"   : "logs/emails",
					"autoExpand" : true
				}
			}
		};
	}

	function testing(){
		structDelete( variables, "mailsettings" );
	}
}

This configuration writes each mail to a log file in development, ignores mails in testing, and uses Postmark in production. Postmark is an excellent transactional email service that we have been using for a while at Ortus. We even bundle the PostmarkProtocol with cbMailServices because we like it so much.

Finally, MJML is a responsive email framework. Responsive design in emails is not as simple as responsive design for websites. For that reason, we use MJML templates. It compiles down using Webpack and ColdBox Elixir to beautiful, responsive email templates. There is even a great Visual Studio Code extension that allows us to preview it inline.

Sentry

Sentry is our error monitoring platform of choice. It is a free, open-source appliance that you can self-host yourself. You can quickly wire up a ColdBox app using the Sentry module. We also use Sentry on the front-end to capture JavaScript errors.

Stripe

Our application is set up to take multiple payment providers, but we really love Stripe. Stripe makes it easy to do all sorts of purchases and subscriptions. Their Stripe Elements UI library powers the credit card fields in CFCasts, and best of all we are backed by their amazing secure infrastructure. The code to interact with Stripe is done with the amazing stripe-cfml library by John Berquist. We hope to never have to find another payment provider, because it would be hard-pressed to find one as good as Stripe.

Inertia.js, Vue, cbInertia, and ColdBox Elixir

In clicking around CFCasts, you might have noticed how fast and snappy the UI is. Some of you may have thought that this must be a single-page application (SPA) talking to an API. Not quite. This application is built with Inertia.js, a library for building "modern single-page React, Vue and Svelte apps using classic server-side routing and controllers." There is no API in CFCasts. There is no vue-router. Inertia.js powers the SPA experience while letting us use all the power of ColdBox we know - handlers, relocate, authentication, flash scopes, all of it. If you want to use modern JavaScript frameworks to build your site but you don't need an API for things like mobile apps or third-party integrations, save yourself the time and effort and use Inertia.js.

Alongside Inertia.js we chose Vue. At Ortus we love both the simplicity and scaling of Vue. It can be easily sprinkled in an existing application or it can power the entire front-end like in CFCasts. I'm sure React and Svelte are great, but, in my opinion, Vue is just as good while feeling familiar and easy to understand.

The cbInertia module is the glue that ties Inertia.js to our ColdBox application. It is very straightforward to get started, especially when using the quick-tailwind-inertia ColdBox application template.

Both that template and CFCasts use ColdBox Elixir to build our front-end assets using Webpack. ColdBox Elixir let us skip configuring a complex build system from scratch and hit the ground running building CFCasts. For more information on Inertia.js and cbInertia, you can check out the webinar I gave on for free on CFCasts. An entire step-by-step series will also be coming to CFCasts for subscribers.

Tailwind CSS and Tailwind UI

I cannot talk enough about Tailwind CSS and what it has done for teaching me CSS. I had a lot of imposter syndrome and anxiety about "doing CSS the right way". BEM and OOCSS were confusing and didn't answer most of my questions. At the same time, whenever I had to do any styling change, I'd end up fighting existing styles and making a mess for myself in the future. Tailwind taught me how CSS worked, how to make something look great, and, most importantly, taught me how to stop thinking there was just "one right way".

Another reason I love Tailwind is that I know all the styles from project to project. I don't need to scan huge SASS files and includes - I can use the same Tailwind classes I already know. This doesn't mean the sites look the same. The Tailwind config file can completely change the look and feel of a site. But that power comes without having to invent a new styling system for each project. So it combines the familiarity of Bootstrap with the customization of a home-grown design system. It is fabulous.

Recently, the Tailwind team released Tailwind UI, a paid collection of components to quickly put together a website. We immediately purchased a license and used it to build out the first version of CFCasts in less than a week. We highly recommend Tailwind UI to anyone who has embraced Tailwind CSS.

cbValidation

All our forms are checked with cbValidation. With new helpers in v2.0.0 like validateOrFail the process is almost automatic. Even creating our own validators is so simple using udf callbacks or custom validators. In CFCasts, we only do server-side validation. The feedback you see in your browser is from cbValidation. Inertia.js makes this not only less duplication but just as quick as client-side validation.

Vimeo

Vimeo hosts all of the videos on CFCasts. It is reasonably priced with excellent privacy controls. The Vimeo player powers the viewing experience on CFCasts and is best in class. It was nice to get all of the functionality we wanted without having to build it from scratch.

GitLab

We host our source code on a self-hosted GitLab instance. Additionally, GitLab Pipelines power our CI system. This executes our tests, checks our formatting, runs our migrations, and even builds and deploys our app. All of this can be done using our CommandBox Docker images straight from Docker Hub. We are big fans of GitLab and GitLab Pipelines at Ortus.

Wrap Up

So there you have it. We hope this gives you ideas on how to modernize your own applications. We look forward to you subscribing to CFCasts and enjoying all the content that is there and coming soon!

Add Your Comment

(1)

Jan 12, 2021 19:15:52 UTC

by Jonas Eriksson

Super happy to see that you use TailwindCSS & recommend TailwindUI as well - we've found it extremely useful. Nice to see more "big CF players looking past Bootstrap" :) Tailwind just feels insanely productive due to the complete lack of SASS or external CSS files (like you wrote as well)!

Recent Entries

Why BoxLang When You Have Kotlin, Groovy, Scala, and more…

Why BoxLang When You Have Kotlin, Groovy, Scala, and more…

As we approach a stable release of BoxLang and our continued marketing reaches more folks, many have asked about its purpose. Why create a new language when the JVM ecosystem already includes established languages like Kotlin, Groovy, and Scala, to name a few.

Luis Majano
Luis Majano
December 18, 2024
ColdBox Free Tip 6 - Using Routing with Wildcard Domains!

ColdBox Free Tip 6 - Using Routing with Wildcard Domains!

ColdBox gives you the flexibility to create domain-specific routes, making it perfect for multi-tenant applications or projects that need to respond differently based on the domain or subdomain being accessed. In this tip, we’ll dive into how to use the withDomain() method to create routes that match specific domains or sub-domains.

Maria Jose Herrera
Maria Jose Herrera
December 18, 2024