CFLib moves to Node.js

This post is more than 2 years old.

This isn't necessarily new per se, but as I just completed some small tweaks I figured I'd share that I've migrated CFLib to Node.js. For the most part the conversion was fairly simple, but I thought I'd share some of the highlights (and issues) of the new code base.

First, let's talk about the old stack. The code base was very old. The last revision was written in 2008. It used ModelGlue and Transfer. MySQL was used as the database. For the most part, the site ran fine. I ran into issues with Transfer if I accidentally saved a UDF with the same name as another but as that was pretty rare I never got around to investigating it or even fixing it. I started on a FW/1 rewrite over a year ago and actually got everything but the admin done, but I hate writing admins so much I never got around to finishing it. (Yes, that is lame. And guess what part I just finished in the Node version?)

Content submissions have slowed down quite a bit, but honestly, I put the blame on me for that. Adam Cameron helped out a bit for a while, as have others, but at the end of the day, I was the sticking point for things getting released, no one else. I apologize for that. The site still gets "ok" traffic, nearly 100K page views last year, so I'm hoping that having a new home for it will give me less of an excuse to ignore it. Y'all can nag me if I don't do a better job this year.

For the new site, my stack couldn't be more different:

  • Node.js, of course.
  • Express 4.X
  • MongoDB
  • Mongoose (a wrapper for Mongo)

As I rebuilt the site, I decided to kill off some features, the biggest being ratings (there were maybe 200 ratings) and author pages. I can return author pages if people feel strongly enough about it. I also switched the code renderer to PrismJS, my favorite library for code coloring.

You can see all the code up on GitHub: https://github.com/cfjedimaster/cflibwww. Before you look at the code, please be aware that I'm a Node noob! I've played around a lot with Node and have released multiple sites, but I still feel like a first year freshman with it. I'd say my code is slightly better than my first year ColdFusion code, but only slightly.

This is - I think - the largest site I've built in Node.js so far. I feel like the biggest issue is my core application file, index.js. It isn't too big (323 lines currently), but it feels messy. When I began working on an Admin controller, I moved code for those routes (think code that handles a particular request) into a unique file, and I'd like to do the same with the main views as well. Ditto for all the helpers I wrote in Handlebars.

Basically - I can look at my code and at least "smell" the things that seem wrong. I think that's good.

Mongo is - of course - a pleasure to work with. I won't pretend NoSQL DBs are a silver bullet, but my God, if I never write another line of SQL again I'll be happy. Working asynchronously can be a bit of a pain at times. So for example, I needed a way to get my libraries, and for each library, get a count of the number of UDFs in it. I used a library named async which allowed me to take those N async calls and listen for them to complete:


this.find().sort({name:1}).exec(function(err, libs) {
	async.map(libs, getUDFCount, function(err, result) {
		for(var i=0, len=libs.length; i<len; i++) {
			libs[i].udfCount = result[i];	
		}
		locals["libraryCache"] = libs;
		cb(null,libs);
	});
});

The thing that bugged me the most, and I never really realized it before, is how limited Handlebars can be at times -- specifically in terms of doing anything in logic. Handlebars wants to minimize the amount of logic you include in your views, and I can get that, but it really bit me in the rear at times. As an example, when editing a UDF, I need to display a list of libraries the UDF can belong to, and then set the current library as the selected item. Handlebars supports IF clauses, but the expression must be simpler. So you can't do: {{if something is something}}. Nope. You have to actually write a helper function for it:


selected:function(option,value) {
	if(option == value) {
		return ' selected';
	} else {
		return '';
	}
}

Here's how that looks in the Handlebars template:


<tr>
	<td><b>Library:</b></td>
	<td>
	<select name="libraryid">
	{{#each libraries}}
		<option value="{{this.id}}" {{selected this.id ../udf.library_id}}>{{this.name}}</option>
	{{/each}}
	</select>
	</td>
</tr>
<tr>

Not horrible, but not ideal either. Also, ColdFusion spoiled me. Being able to do this: dateFormat(something, mask) is nice compared to how I did it in the new site. First I got the MomentJS module. I then used it in a Handlebars helper. Finally I called it from my template like so: {{fullDate udf.lastUpdated}} That's readable at least, but not necessarily simple.

All in all though, I'm happy with this new version, and I've got three UDFs waiting to be released. I'm going to release those later in the week. For those of you who are Node/Express experts, I will gladly take your feedback. For those who don't know Node and have specific questions about the code base, ask away!

It's come a long way from this...

cflib2

By the way - that wasn't the first version. Unfortunately I couldn't find it via the Wayback Machine.

p.s. This new version will have broken the API used by the ColdFusion Builder extension. If anyone actually made use of that, speak up, and I'm sure I can get it working again.

p.s.s. The next conversion will be ColdFusion Bloggers.

Raymond Camden's Picture

About Raymond Camden

Raymond is a senior developer evangelist for Adobe. He focuses on document services, JavaScript, and enterprise cat demos. If you like this article, please consider visiting my Amazon Wishlist or donating via PayPal to show your support. You can even buy me a coffee!

Lafayette, LA https://www.raymondcamden.com

Archived Comments

Comment 1 by Andy Matthews posted on 1/5/2015 at 10:11 PM

Is it still dynamic? You should write static pages, or perhaps cache them.

Comment 2 (In reply to #1) by Raymond Camden posted on 1/5/2015 at 10:15 PM

Heh, well, it is Node, so yes. ;) I considered HarpJS, but, I only wanted to go that route if I was going to EOL (End of Life) CFLib, and I don't want to do that. I do think it will continue to grow over the year. I felt like Harp would be a bit too complex to get working.

Comment 3 by Marcin posted on 1/5/2015 at 10:24 PM

Not sure if you've written about this before Ray, but how do you deploy your Node stuff? Do you use Heroku or similar?

Currently working on my first full NodeJS / Express site (a side project), running it on my own VPS though - running it behind Nginx, with "forever" to keep it running, and using Capistrano to do the deploy. All working well for now, but feels a bit cobbled together.

Comment 4 (In reply to #3) by Raymond Camden posted on 1/5/2015 at 10:27 PM

I use AppFog. I reviewed them a while ago. I prefer Modulus, but for the price, AppFog is better for me.

Comment 5 by Ryan Guill posted on 1/6/2015 at 1:25 AM

Nice stuff, and thanks for open sourcing it. I haven't written any express apps yet, but we have been writing quite a few node apps and really enjoying it. I definitely sympathise with you about code organization though, knowing when and how to split files is something we are still working on.

With mongo, did you run into anything complicated with CFLib that made you wish you had something more like SQL, or was everything pretty straight forward? I actually like SQL, but would like to play more with the NoSQL stuff - I'm currently leaning towards postgres with the JSON columns to get the best of both worlds, but interested in real world experience with mongo.

As far as suggestions go, if you aren't averse to adding a few packages:

https://www.npmjs.com/packa... is great for multi layer configuration - you can take configuration from files, arguments, environment variables and have defaults - and you can configure what takes precedence.

https://www.npmjs.com/packa... has tons of utility functions for collections and other things that make a whole lot of your data massaging easier. For instance, a reduce in your udfcount code would clean up a lot of the boilerplate and reduce the signal to noise - I believe there is an Array.reduce built in with node, but lodash has a lot of other stuff too. (If you would rather go more functional, https://www.npmjs.com/packa... looks nice - its similar though. Lodash is much more widely used)

JSHint with a .jshintrc file can help with the organization too, at least insofar as it will help keep everything consistent.

Comment 6 (In reply to #5) by Raymond Camden posted on 1/6/2015 at 2:30 AM

Hmm. I ran into a few oddities - but mainly because I was very unfamiliar with Mongo. I had used it years ago with CF (and in fact, I use cfmongo to create my database, I should have mentioned that), but it had been quite some time and I wasn't familiar with the APIs. Add Mongoose on top, which is cool, but I was also unfamiliar with it too. Everything was easy, I just had trouble learning (well, learning enough to use it) both at once and getting confused between the two of them.

I liked that my complex data (args for UDFs) went from a wddx packet in the database to actual arrays of objects in Mongo.

I also ran into an issue searching by name. I had to add a duplicate property, lname, to let me match names and ignore case.

Outside of that, I think it was all kosher.

Comment 7 by Aaron posted on 1/6/2015 at 2:40 AM

I just have to ask. A ColdFusion library site that isn't running on ColdFusion?

Comment 8 (In reply to #7) by Raymond Camden posted on 1/6/2015 at 2:46 AM

Totally fair. :) I'm slowly turning off my main server which means shutting down my CF box. I've got one Google Compute engine for this blog and AppFog for Node apps. Trying to simplify.

Comment 9 (In reply to #8) by Jean Moniatte posted on 1/6/2015 at 8:03 AM

Congrats on the node.js migration, but I have to agree with Aaron, it's weird that the site is not running on ColdFusion. Plenty of options exist for running ColdFusion at very low cost, your personal server situation should not be the question. Especially for a site that has been fed with content over the years by the community.

Comment 10 (In reply to #9) by Raymond Camden posted on 1/6/2015 at 11:22 AM

"Plenty of options exist for running ColdFusion at very low cost"

Sure, but I also wanted to use something I already had - AppFog. I pay AppFog 20 bucks a month for N instances, so I had one to spare.

"Especially for a site that has been fed with content over the years by the community."
Not sure how this applies. Are you saying people who submitted content may be upset? Their content is still there. I didn't take it away.

Comment 11 (In reply to #10) by Jean Moniatte posted on 1/6/2015 at 11:27 AM

I think that being a website about ColdFusion it should be powered by ColdFusion. That's all I wanted to say.

Comment 12 (In reply to #11) by Raymond Camden posted on 1/6/2015 at 11:29 AM

Ok. I don't agree - but ok. :)

Comment 13 (In reply to #12) by markdrew74 posted on 1/6/2015 at 3:45 PM

railodocs.org is powered by nodejs too,

Comment 14 (In reply to #13) by Jean Moniatte posted on 1/6/2015 at 3:52 PM

All right then. I don't get it, but all right :-)

Comment 15 (In reply to #12) by David McCan posted on 1/6/2015 at 6:01 PM

Imagine this: A guy works for Ford designing cars. When his daughter graduates college he buys her a Toyota. Whatever his reasons, it is easy to interpret that decision as making some kind of statement. For example, the guys neighbor sees the new car and thinks, "Wow, he must not have much confidence in Ford cars if he buys Toyota for his family."

As a luminary in the ColdFusion community, when you migrate your sites from ColdFusion to something else, likewise it might be seen as making some kind of statement, whether you meant it to or not. Jean's comment might be prompted by the thought that when visitors to CFLib or ColdFusionBloggers see they are written in Node.js those visitors might think something along the lines of "Wow, they don't have a lot of confidence in ColdFusion if they don't trust it to run their sites."

I understand that you are "scratching your own itch" and working with Node.js because you want or need to learn about it, while providing helpful resources for others. I understand that you are not "disrespecting" ColdFusion by doing a project with Node (queue Rodney Dangerfield), but perhaps on a "meta" level you made a statement whether you meant to or not.

The Node and PhoneGap articles (and your 2014 reading list for that matter) are interesting to me, just like your ColdFusion articles. It is cool that you are exploring and sharing across a wide range of web technologies and not limiting yourself to ColdFusion. I have a lot of appreciation and respect for you. Thank you for sharing.

Comment 16 (In reply to #15) by Miguel-F posted on 1/6/2015 at 6:21 PM

Well said...

Comment 17 (In reply to #15) by Raymond Camden posted on 1/6/2015 at 6:41 PM

I think that's a fair statement to make. I *do* (currently) work for Adobe and I can see how it does make a statement when I use other technologies than CF.

If there is any "meta statement" I'd like folks to get it is that there are more options for the server-side stack than CF. I think folks already know that, I knew that, but I never really did much else outside of CF over the past decade or so.

I feel like I've evangelized, unofficially, ColdFusion, for most of my life, and honestly, I don't know if I can anymore. I still care about CF. I want to see CF12 - and I want to test it, file bugs, etc. I still want to present on it (I just did one a few weeks back). But I don't know if I want to be "responsible" any more for trying to get it to succeed. I tried for 10+ years to officially become the evangelist for the product and it never worked out. I stood by while others, normally people I had never heard of, get selected for that role. (To be clear, good people. :) Maybe that's a sign that I'm not the best luminary for it. Maybe I'm taking it too personally. (Likely, I have the fragile ego of a teenager.)

All I want is to help others. That was originally just for CF, but now is more client-side, hybrid dev, "general web", etc, with CF just being one part of it.

I'll probably regret writing this - but I'm beyond caring. (To be clear, beyond caring about "product", not about yall folks here in this conversation, I *hope* that is clear!)

Comment 18 by Adam Cameron posted on 1/6/2015 at 7:29 PM

Jesus. One would need to have a very very narrow view of our industry to pass judgement on Ray for using something other than CF for the cflib.org site.

Ray is NOT an evangelist for CF (well: in his spare time he is, but it's not his job), and as far as I know he has nothing to do with CF at Adobe. Also cflib.org is not an Adobe site, or an "official" project. Plus he does it in his spare time. So it's completely up to him what he chooses to use for it.

Equally, how bloody naive to think that just because Ray is a CF expert and *likes* it that it is immediately either the automatic best fit for what he's doing at a given point in time? That's gobsmacking. Even before stopping to consider he might simply be a bit bored with CFML - he's been doing it for an age, and its appeal as far as "doing new stuff" wears off after a while, I can tell you - and he simply might have used this *personal project* as an opportunity to learn something new and compelling.

What a bunch of - foolish - ingrates.

Also... quick show of hands... who amongst the commenters here have CFML-run community-aimed websites that you pour a lot of time into, and have made the CFML community a much much better place? [Adam is amongst those not raising his hand].

Comment 19 (In reply to #17) by David McCan posted on 1/6/2015 at 8:12 PM

You weren't the ColdFusion product evangelist? Wow, I guess sometimes the virtual has a more significant impact than the real.

Comment 20 by Michael Zock posted on 1/6/2015 at 9:11 PM

I wouldn't write off relational databases just yet. Both major types have their advantages and disadvantages (and don't forget graph databases, but that's a different can of worms).

As for using Node instead of CFML, part of a developer's job is to pick the most efficient tool for the job at hand (Why use a giant 18-wheeler when a panel van can get the job done in a shorter time?) and to know that the decades old way isn't the only way of doing things.

Comment 21 (In reply to #19) by Raymond Camden posted on 1/6/2015 at 10:37 PM

Thanks David. It wasn't for lack of trying...

Comment 22 by John Allen posted on 1/8/2015 at 4:48 PM

This is cool! Have you checked out GeddyJS as a front controller framework? It's a little more prescriptive than Express (which I also like). I've been using both and Geddy just 'feels' right as an MVC implementation especially coming from the family of ColdFusion frameworks.

Comment 23 (In reply to #22) by Raymond Camden posted on 1/9/2015 at 12:18 PM

I have not. I took a look at it yesterday and it looks interesting. Going to try to play with it soon.

Comment 24 (In reply to #8) by Aaron posted on 1/11/2015 at 4:12 AM

If I'd known you'd receive the types of comments my question generated I would have kept my mouth shut. Your a champ for the CF community, the comments are undeserving.

Comment 25 (In reply to #24) by Raymond Camden posted on 1/11/2015 at 3:27 PM

Spirited discussion is what makes this community so fun. ;)

Comment 26 (In reply to #18) by gary gilbert posted on 1/13/2015 at 4:58 PM

can I NOT raise my hand too?