From Apache/PHP to Node.js & Express

If you’re part of an engineering team that is currently using PHP and thinking of moving to Node.js, this post is for you. We won’t go into the nitty-gritty of porting PHP to Node.js and there will be no basics of Node.js. We will cover our decision-making; a description of where we started; higher-level considerations on writing a Node.js server; and, finally, our deployment strategy.

Why Port?

At 1stdibs, we made the decision to move from Apache/PHP to Node.js & Express for five main reasons:

  1. Less Code
  2. Full JS Stack
  3. Developer Happiness
  4. Return on Investment
  5. Future Optimizations

Less Code

1stdibs is built upon a service-oriented architecture (SOA). We have a back-end of Java services that the front-end consumes. This meant that we needed to maintain front-end models and templates in both server-side PHP and client-side JS. If we could rid ourselves of the PHP, we reasoned, we could consolidate front-end representations of back-end models in one language: JavaScript (as well as consolidating some templates). From a code maintenance perspective, there’s simply less code and no duplicated logic.

Universal JS FTW!

Full JS Stack (& the benefits thereof)

Having your stack in one language is just simpler. For developers, the lack of context switching makes them happier and more productive. Then, there’s the added bonus of making the tooling simpler. Instead of the two package managers we were using, Composer and npm, we’d need only one. Composer is great, but our tooling and client-side dependencies are handled by npm, so we’ll always need npm. Once we finish deleting all the PHP, we’d be left with npm as our one and only package manager.

Developer Happiness

It’s important to us to ensure that our developers are expanding their skill-sets and developing their careers. For JavaScript developers, Node.js is extremely compelling. The ability to work on the server using the same tools, idioms and patterns as you do on the client is incredibly empowering and productive. Plus, Node.js is becoming incredibly popular and gaining huge traction in the enterprise. Node.js experience is important for JavaScript developers to have.

Return on Investment

We’ve put a lot of resources into hiring great JS engineers and training less-experienced JS engineers to be great. Our client-side stack is sufficiently complex that we need developers with a high-level of JavaScript ability. We weren’t hiring for PHP, we were hiring for JavaScript. Our thinking was, why not leverage their skills on the server as well?

Future Optimizations

Longer term, our plan is to break two monolithic applications into smaller apps that can be deployed individually. This will be easier using Node.js, Express and npm. Theoretically, we could do something similar with PHP (e.g. using Slim). But we wouldn’t get any of the benefits enumerated above, we just get a bunch more complexity: operations would be more complex with Apache/PHP and scaffolding becomes just… weird.

Picking a Framework

The PHP app that we eventually replaced with Node.js had a few key responsibilities:

  1. login and authorization
  2. routing and server-side templating (i.e. serving HTML)
  3. bootstrapping the client-side apps
  4. proxying to services (sidestepping CORS)
  5. serving static assets (JS, CSS, images)

This was the base functionality that we would need to replace.

We looked at lots of frameworks, but settled fairly quickly on Express (check out the spreadsheet we used to evaluate). Any framework that wasn’t sitting atop Express seemed like a gamble. Express is well-understood and very well-documented. Plus, it’s possible to find people with specific Express training.

We added a few core modules from kraken (express-enrouten for routing and lusca for security); plus i18n-node for internationalization and Swig for templating (swig has subsequently become abandoned – oops – the dangers of OSS).

We considered adopting kraken fully, but we were using Twig for server-side templates in PHP and switching to Swig was very fast and straight-forward. Plus, we didn’t like Dust nor the i18n support in kraken.

Write The Server

After we decided on a framework, it was time to write the server.

Using Apache and PHP, you don’t write a server per se. Apache is the server and PHP is the application. With Node.js the server and the application are one. When switching to Node.js from Apache/PHP it’s important to address functionality you may have taken for granted when using Apache. With Apache, you (or a system administrator) configure the server then, in the PHP application, you don’t really need to worry about certain things that Apache is handling for you. Node.js works differently.

serving static files

In Apache, serving files is, needless to say, core functionality. Not so with Node.js. You configure static file serving in the application. Fortunately, serving static files is simple, well-documented and built into Express.

logging

Most basic Apache configurations give you access and error logs out of the box. With Node.js, once again, you’ll need configure this in the application. Luckily, there’s some great OSS packages that make this pretty simple. Morgan is a basic request logger that is simple to set-up and allows you to log to an output stream (e.g. stdout or a file). If you need to log to a database or have other (e.g. more advanced) logging needs, check out winston.

proxying

A base piece of functionality we needed was the ability to proxy-pass client-side AJAX requests to our services. We feel that proxying all service requests through the domain from which it originated is simpler than dealing with CORS headers. Plus, if you want to use webpack-dev-server via a proxy (as we are doing), you’ll need a way to handle that in your Node.js app. We found http-proxy a simple and reliable solution.

All the other stuff

Along with the considerations above, we had a bunch of other work to do. Our starting point was an MVC application built atop CodeIgniter (CI) serving a suite of single page apps. The bulk of our work was porting:

  • CI controllers to Express route handlers and middleware (including login and authentication)
  • templates from Twig to Swig (trivial)
  • data access from services (in order to bootstrap the client single-page apps properly)

A key component missing from the list above is our CodeIgniter models. That’s because we didn’t need to rewrite our PHP models! FTW! We used the Backbone models that we had written for the client-side apps. Of course, we needed to extend Backbone.Model.sync so that it worked universally on the server and the client.

Deployment

If you have an app of any size and you’re considering one big release, don’t do that. Roll out slowly in a staged deployment. We took weeks.

Some benefits of a staged deployment:

  • minimizes surface area for bugs
  • one to two engineers can handle the development as you’re dealing with a handful of routes and/or features per release
  • provides minimal disruption to ongoing feature development and enhancements – new stuff can continue to be released (this can lead to duplicated efforts).
  • if you do it properly, you’ll be able to rollback instantly to the previous server

NGINX FTW

How do you roll out slowly? Put a server in front of your servers. We used Nginx.

             +----------+
 http        |          |---> Apache/PHP
 request---->|  Nginx   |
             |          |---> Node.js
             +----------+

This allows you a great amount of freedom as you can “turn on” a route at a time (and turn it off if things go sideways – as they did a couple of times for us). We also found it helpful to be able to turn on routes without doing a code deploy. This provided us with some wiggle-room around our once-weekly release schedule.

There is a downside. You’ll need to ensure that your client-side code works with both your old Apache/PHP server and your new Node.js server. This doesn’t sound horrible, but you may need to port non-optimal functionality from the old server to the new server. Just hold your nose when you do it (and file a ticket to clean up the tech debt).

Summary

From start to finish, it took about a year to make the entire transition. This may sound ridiculous, but that time-frame included the decision making process (we were in no hurry); writing a core framework atop Express that fit our own needs; porting everything; and the slow, staged rollout. And, remember, we had only 1-2 developers working on it – on a part-time basis – the entire time.

If you’re thinking about making the jump, consider carefully. How does it benefit your team, how does it benefit your larger organization? If you’re part of business organization, remember that the business will need to continue to function. You’ll need to balance important business goals with your engineering goals.

We’ve been very happy that we made the jump.