NodeJS on DreamHost

I recently wrote a simple nodejs/react/express CRUD app talking to a DH mysql back end.  Before this situation I had been deploying these types of apps to Heroku or Google Cloud/Firebase Hosting.  Part of my reason for doing this little project was to test out the process for deploying a standard nodejs web app to DH.  The good news is it actually worked without too much effort on my part (and without doing anything a regular user can’t do) and the existing DH documentation was very helpful.  The less good news is the documentation is out of date in parts and the process was more manual than it should be.

App Code Structure

This is a very common structure for client-side web apps written in nodejs, and it’s a bit different from the typical web apps DH is optimized for hosting.  There are people on the DH team who know this stuff much better than me so I’m just including a high level overview for anyone unfamiliar.  If you know how these things work you can probably skip to the next section!

The web app itself is really two nodejs apps, the front-end client side React app and the back-end API server.  

The front-end app runs entirely within the user’s browser which makes it able to do certain things very quickly and be generally very responsive from a UI perspective.  The trade off is that any user can see everything going on, manipulate the running code, and directly interact with any back-end APIs.  That makes securing access to the data a whole different animal.

That’s where the back-end API server comes in.  The client side app is built to only interact with the back-end server which handles any authentication, authorization, and data access.  This API server is typically a persistent process, which makes these apps harder to host on shared hosting.  

These types of apps also typically include their own web server to serve the bundled static javascript of the client app as well as any associated images, CSS etc.  In this case the API server also functions as the web server, which is handy for embedding simple authentication layers.

During development all of the code for the two main parts of the application run locally on my computer, accessing the mysql server directly.  I had a fully working app ready to deploy!

Deploying to Production

Finally I was ready to deploy!  I started by looking at the pretty thorough DH provided documentation on Node.js hosting.  That was a good starting point but I ran into multiple issues.

Problems with DH Standard Configuration

There’s a few main issues with hosting node.js apps:

  • The installed mod_passenger is useless for node.  It’s just way too old.
  • The installed version of node is also so old it’s basically useless.  Would be better to not bother installing this version.
  • Without mod_passenger it’s a bit harder to deploy apps behind SSL.  It does work (mostly) to stick it behind apache with our mod_proxy setup and that’s what I did.  Alternatively you’d have to set it all up manually, which is obviously annoying.

mod_passenger

Since I was the person who first brought mod_passenger into DH production use way back when Ruby on Rails was cool, I assumed that would do everything I needed.  It turns out that mod_passenger is effectively useless because it can’t host node.js apps newer than version 13.  That’s really out of date in nodejs land.. Like 3+ years.  I don’t know the reason for mod_passenger node support being so out of date so if anyone does know, let me know.  Can it just be upgraded?

node.js installed version

The installed version of the node binary is version 10!  Version 18 is the current LTS version, and version 19 is already out.  Node version 14 was just end of lifed at the end of April 2023.  It’d be better to not even have any node version installed.

SSL and proxy

It’s possible to run an ExpressJS server with SSL and make it the full front end but it’s generally not recommended for production apps.  For VPS DH supports proxy configuration for both apache and nginx (I believe), which helps a lot.  The one issue I’ve had is making my app only accessible behind the proxy server.  There’s probably a way but my first basic attempt didn’t do it… which means it’s a friction point for users.

Manual Server Configuration

Those problems with the DH standard config forced me to do some manual server configuration.  Fortunately, DH provides documentation on how to install a custom version of NVM, which is then used to install and manage versions of node.js.  This was straightforward for me, but would be a dead end for many users.  Automating this process somehow could be very slick.  Also, there’s one part where you are forced to edit the .bash_profile file… seems like something DH could do ahead of time.

Once I did that I was able to upload my bundled React app and the associated server.js file, start it up, and access the app on port 8080!  Success!  

From that point it was straightforward to set up a proxy through the DH web control panel, and set up free SSL.  The additional step is to make the server.js process persistent, and there are multiple ways to do that.  I’ll discuss that a bit more in the Additional Notes section later!

Updating The Code

This isn’t too bad, but it’s a friction point compared to the norms of “jamstack” / nodejs oriented hosts.  When I want to deploy a new version of my code it’s a multi step process.  I have to first build the app (npm run build), then upload the changed files (using rsync or sftp sync or something), and also upload the server.js file if it was changed (it won’t be most of the time). 

Services like Heroku provide their own cli tools and are also connected directly to your git repository.  When you want to push out an update you do it by running a single command, which pulls down the latest code and does everything else needed to deploy a fresh container.  It feels like a natural part of the development workflow.  They also automatically set up SSL, the front-end proxy, things like CDN, etc, and provide a way to inject environment variables into the code, which is handy for keeping the code clean.

Additional Notes

pm2 for persistent processes

To make sure my server.js process is always running, I needed to do something more.  I first read the DH Help Article on running outside of Passenger and noted that says to use a tool called Forever.  That tool is actually deprecated and recommends using either pm2 or nodemon.  I actually use nodemon as part of my development toolkit and it’s great, but pm2 is basically awesome and I used that for production.  Some sort of automation around pm2 could be a very useful tool for users.

A couple extra niceties

  • Mosh: I use mosh as much as I can to connect to servers and it’d be very nice to have that pre-installed.
  • “python” doesn’t seem to be mapped to anything by default, and probably should be.

Additional Reading

These were all helpful to me as I worked through this:


Posted

in

Tags: