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...
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.
Archived Comments
Is it still dynamic? You should write static pages, or perhaps cache them.
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.
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.
I use AppFog. I reviewed them a while ago. I prefer Modulus, but for the price, AppFog is better for me.
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.
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.
I just have to ask. A ColdFusion library site that isn't running on ColdFusion?
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.
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.
"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.
I think that being a website about ColdFusion it should be powered by ColdFusion. That's all I wanted to say.
Ok. I don't agree - but ok. :)
railodocs.org is powered by nodejs too,
All right then. I don't get it, but all right :-)
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.
Well said...
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!)
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].
You weren't the ColdFusion product evangelist? Wow, I guess sometimes the virtual has a more significant impact than the real.
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.
Thanks David. It wasn't for lack of trying...
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.
I have not. I took a look at it yesterday and it looks interesting. Going to try to play with it soon.
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.
Spirited discussion is what makes this community so fun. ;)
can I NOT raise my hand too?