πŸ— Creating An Express Server

Photo by Kelly Sikkema on Unsplash

This is part 4 of my series on server-side rendering (SSR):

  1. πŸ€·πŸ»β€β™‚οΈ What is server-side rendering (SSR)?
  2. ✨ Creating A React App
  3. 🎨 Architecting a privacy-aware render server
  4. [You are here] πŸ— Creating An Express Server
  5. πŸ–₯ Our first server-side render
  6. πŸ– Combining React Client and Render Server for SSR
  7. ⚑️ Static Router, Static Assets, Serving A Server-side Rendered Site
  8. πŸ’§ Hydration and Server-side Rendering
  9. 🦟 Debugging and fixing hydration issues
  10. πŸ›‘ React Hydration Error Indicator
  11. πŸ§‘πŸΎβ€πŸŽ¨ Render Gateway: A Multi-use Render Server

Over the previous three posts in this series we have described what server-side rendering (SSR) is, created a simple application using React, and discussed the architecture of a privacy-aware server to ensure we understand some of the sharp edges around SSR. Now, we will actually implement a basic server. Just as with the React application we created, the server we create will not be a complete solution, but it will provide a foundation from which we can continue to explore SSR.

✨ A New Project

Where do we start? Well, we need a server that can receive web requests and respond to them. For that, I am going to use Express1, but first I need a project.

NOTE: Where you see yarn, know that you can use your own package manager as you see fit.

  1. Add a new repository on GitHub (or your source control platform of choice).
  2. Make a new folder locally for your code
  3. cd to your new folder and run git init
  4. git remote add origin <your github repo URL>
  5. git pull origin master
  6. git branch --set-upstream-to=origin/master
  7. Create and commit a .gitignore file
  8. Initialize it for JavaScript package management with yarn init
  9. Run yarn install to generate the lock file
  10. Commit the yarn.lock and package.json to the git repository

Great, so now we have a project we can start working on. Let's add Express.

yarn add express

This should update our package.json and yarn.lock, so don't forget to commit those changes. I also recommend pushing often to your remote repository, that way your code is backed up online in case your computer suffers a nasty accident2.

πŸ‘‹πŸ» Hello World!

At this point we need to write some code. We need to setup a route for our server that can handle providing a rendered result for any URL that our application might have. There are a couple of ways we could do this:

  1. Assuming that our server is invoked by some intermediate layer, such as a cache, we could have the server implement a single route (e.g. /render) and pass the URL to be rendered as a query parameter.
  2. Our server could assume the URL is to be rendered by the client code and just accept any URL.

Option 1 gives us a great deal of flexibility in what our server can do, but it forces us to ensure that there is a layer between the original browser request and our server, as something has to be responsible for constructing the appropriate /render route request. Option 2 removes the need to have an intermediate layer, but it perhaps restricts us from expanding server functionality. Of course, option 2 can be changed to option 1 if the need arises, so we can go with option 2 for now, knowing that later, it can be updated to suit changing needs.

Normally, I would add lots of other things to this server to improve development and runtime investigations, such as linters, testing, and logging, but for the sake of brevity, right now we will stick to the main functionality.

const express = require("express");

const port = 3000;
const app = express();

app.get("/*", (req, res) => res.send("Hello World!"));

app.listen(port, () => console.log(`Example app listening on port ${port}!`));

This is our index.js file. It is not doing a lot. On line 4, we create our express app. On line 6, we tell it that for any route matching /*, return Hello World!. On line 8, we tell it to listen for requests on port 30003.

If we run this app with node index.js, we can go to our browser, visit any route starting with localhost:3000 and see the text, Hello World!. This is fantastic. We have a server and it is responding as we hope. Since we are going to run this often as we make changes, I will add a script to our package.json to run node index.js for us.

{
  "name": "hello-react-world-ssr",
  "version": "0.0.1",
  "description": "A server-side rendering server",
  "main": "index.js",
  "license": "MIT",
  "dependencies": {
    "express": "^4.17.1"
  },
  "scripts": {
    "start": "node index.js"
  }
}

In the package.json file shown above, I have highlighted the section I added containing the new start command. From now on, we can start our app with yarn start. The next step is getting our server to render our React application. Before we do that, consider these questions:

  1. How does the server know about and load the code for our React application?
  2. How does the server get the rendered result to send back?
  3. How do we isolate render requests to avoid side-effects bleeding across requests?

πŸ€” The Hows

The answers to the first two questions have implications beyond the server itself, possibly influencing both our client application and any deployment process.

How our server knows about and loads our client application may affect how our server is deployed. Some server-side rendering solutions involve deploying the client-side code with the server so that it has direct access to the appropriate code, others use a mechanism such as looking up in a manifest to identify the files to load from a separate location (such as a content delivery network (CDN)). Neither of these is necessarily a bad choice – they both have their advantages and disadvantages. For example, deploying the server with the right code means:

  • βœ…The server has fast access to the client application it is rendering
  • βœ…The server can integrate nicely with the client application
  • ❌The render server must be deployed every time the client application changes
  • ❌The server is closely coupled to the client application

Whereas, looking up files in a manifest and loading them from elsewhere means:

  • βœ…The render server rarely requires updating
  • βœ…The server can render more than one application
  • ❌The server will probably need to cache JavaScript files locally or be at the mercy of latency when communicating with the CDN
  • ❌The client applications that the server renders likely need to include custom code to support being rendered by that server

Being aware of how these approaches differ – and they differ in more than just the ways I have suggested, is useful in understanding the trade-offs we must make when implementing our render server. Perhaps answering the second question will help us decided which route to take; consider how will our server get a rendered result of the client application?

Our server is going to invoke a call from the React framework that renders our React application to a string, rather than mounting it inside the DOM of a browser. To do that, it needs a React component to render, so it must load our client application and get the root-level component. In addition, assuming our render server is rendering the entire page and not just the React component, the server is likely going to need to gather additional information, such as which files must be loaded in the page, the page title, etc.

This whole process of capturing the application render and associated metadata requires interplay between the server code and the client code. Revisiting the first question and the two approaches I gave: if the server has the client code deployed with it, the server could know exactly which files to load to render the component, importing those directly and using them accordingly; if the server is less-closely coupled, we likely need some mechanism whereby the client application itself does more of the heavy lifting by hooking into some framework provided by the server, even if that is just exporting a specific object so that server can identify the appropriate things to coordinate rendering.

Ultimately, either we have a server that is custom built to our application, or we have a server that is built to support many applications. What to do? I say, dive in and try them both. To that end, next time we will look at the first option where the server knows all about the client application (though we may cut some corners to get to the salient points), and we will answer that third question; how do we isolate our renders?

πŸ™‡πŸ»β€β™‚οΈ In Conclusion

Herein we have created our server, though it does not do much yet. We have also considered two different approaches to connect our server to our client application: closely-coupled or more open, and we have started to think about how the server will isolate and respond to render requests.

This week's entry turned out a little longer than I had intended, and covered less things than I had hoped. Sometimes that is the way it goes. One of the biggest reasons I write these blogs is to discover what I do and do not know about something. Often in the effort of explaining it to someone else, I identify a bias that I have without any supporting evidence, or a topic I grasp that is far harder to explain than I expected.

Until next time, when we start to implement our server-side rendering, please leave a comment. Perhaps you have a question, a personal experience writing a render server, or want to take umbrage at something I have stated. I look forward to learning with you as we continue this journey into the land of SSR. πŸ—Ί

  1. I find Express easy enough to use and well-supported, though there are other options that one could use instead if one were so inclined []
  2. A lesson from bitter experience; hard drives die (especially SSDs) without warning, drinks spill, laptops get dropped – keep your work backed up []
  3. The port is currently hard-coded for simplicity, but we could make this configurable []

Versioning with T4 templates

In the last month or two I've started using T4 templates, a feature originally made available in Visual Studio 2005 though rarely used outside of code generation tools for database models and the like. Though I have known about T4 templates for quite some time, like many developers, I have struggled to conceive of a practical application in my every day software development activities. That is until recently, when two uses came along at once.

1 day, 7000 lines

The first use came when I started to implement a strongly typed unit conversion framework. I needed to generate types for different quantities (Area, Distance, Volume, etc.), units and operations that convert between them. As I was coding my framework, I noticed that much of the code was identical. Rather than cut and paste a lot (I had over 20 different quantities and hundreds of units) and deal with a horribly tedious task when bug fixing, I realised that a T4 template and an XML file would reduce my effort a lot. I embarked on learning all about T4 and within a day I was auto-generating over 7000 lines of useful code from far less than that.

I am still working on some nitty gritty details in that framework so I'd rather not blog about it in detail just yet. However, having become more familiar with T4 templates, it was much easier to spot other places they could add value. The next opportunity arose when working on another project. One of the first things I tend to do when setting up a development project is set up the versioning. I split common attributes shared by every assembly in my solution into a different file and then link to it in each project.

[assembly: System.Reflection.AssemblyCompany("SomewhatAbstract.com")]
[assembly: System.Reflection.AssemblyProduct("DoobreyFlap")]
[assembly: System.Reflection.AssemblyCopyright("Copyright Β© 2012 SomewhatAbstract.com")]

[assembly: System.Reflection.AssemblyConfiguration("alpha")]

// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Patch
// Revision
//
[assembly: System.Reflection.AssemblyVersion("1.0.0.101")]
[assembly: System.Reflection.AssemblyFileVersion("1.0.0.101")]

This single file is then updated by a script (MSBuild step or pre-build BAT or PS file) based on information from my source control provider of choice. You may wonder where T4 fits in here. Well, it doesn't, yet.

The next step in a development project is to consider deployment. I use Windows Installer XML (aka WIX) for my installers, an open source installation framework that uses XML files to describe the installer. Just as with my source code, I split the version information into its own file as this simplifies update of that information and allows me to share it across different installations. I also update some of this information using a script.

<?xml version="1.0" encoding="utf-8"?>

<!-- These values should be updated when a new release is made. This ensures that the new version can
 upgrade the old. -->

<!-- This is the product version.
 Note: only the first 3 tokens are used (i.e. 1.0.0.0 will be ignored). -->
<?define ProductVersion=1.0.0?>

<?define ProductVersionText="1.0.0.101"?>
<?define ProductState="alpha"?>

<Include />

So, now I have two version files. My scripts update these to include revision information from my source control provider (if you are familiar with semantic versioning, this goes in the build version, though I admit, in my examples I am not using SemVer). However, when doing a new major, minor or patch update, I have to manually edit each of these files to change the major, minor or patch version. On more than one occasion, I've updated one file and not the other. This only gets more complicated if there are additional files that need versioning (maybe there's also VB projects or some markdown for a release notice). This is where T4 templates comes in handy.

1 file, many files

Now, some of you may think that this is where my plan falls down. Can't T4 templates only output one file? How can one file be suitable for C#, VB and XML all at the same time? The answer is, it can't. At least not without a little help.

As you can execute any code in a T4 template, you could just write out a file using a FileStream or something similar. However, while researching T4 templates for my unit conversion framework and realising that 7000 lines of code and tens of different types in a single file might be a tad unmanageable in some circumstances, I discovered a rather handy T4 mix-in that permits me to output multiple files from a single template using a simple syntax.

Using Damien Guard's Manager.ttinclude file, I created a T4 template and outputted my C# and WIX files. Now, updating the version just means updating the template. This could easily be extended so that the information is read from some other file (such as an XML or JSON file) rather than hard-coded in the template. In addition, the revision information that I extract from source control via script could be extracted via the template itself, if I so choose.

<#@ template debug="true" hostSpecific="true"
#><#@ output extension=".cs"
#><#@ include file="Manager.ttinclude"
#><# // Create the file manager so we can output multiple files.
#><# var manager = Manager.Create(this.Host, this.GenerationEnvironment);
#><#
#><# // Setup the version information.
#><# var version = new Version(1, 0, 0, 101);
#><# var configuration = "alpha";
#><# var company = "SomwhatAbstract.com";
#><# var product = "DoobreyFlap";
#><# var copyright = "Copyright Β© 2012 SomewhatAbstract.com";
#><#
#><# // Output the C# versioning
#><# //
#><# // VersionInfo.g.cs
#><# //
#>
[assembly: System.Reflection.AssemblyCompany(<#= company #>)]
[assembly: System.Reflection.AssemblyProduct(<#= product #>)]
[assembly: System.Reflection.AssemblyCopyright(<#= copyright #>)]

[assembly: System.Reflection.AssemblyConfiguration(<#= configuration #>)]

// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Patch
// Revision
//
[assembly: System.Reflection.AssemblyVersion(<#= version.ToString() #>)]
[assembly: System.Reflection.AssemblyFileVersion(<#= version.ToString() #>)]
<#
#><#
#><#
#><# // Output the WIX versioning
#><# //
#><# // VersionInfo.g.wxi
#><# //
#><# manager.StartNewFile("VersionInfo.g.wxi");
#>
<?xml version="1.0" encoding="utf-8"?>

<!-- These values should be updated when a new release is made. This ensures that the new version can
 upgrade the old. -->

<!-- This is the product version.
 Note: only the first 3 tokens are used (i.e. 1.0.0.0 will be ignored). -->
<?define ProductVersion=<#= version.ToString(3) #>?>

<?define ProductVersionText="<#= version.ToString() #>"?>
<?define ProductState="<#= configuration #>"?>

<Include />
<# manager.EndBlock();
#><# manager.Process(true);
#><#+
#>

This template will output two files: VersionInfo.g.cs and VersionInfo.g.wxi. Note that the C# file is named after the template whereas the WIX file is named on line 41. I recommend adding the g suffix to make it easier to see that these are auto-generated files.Β I have this template in a special PreSolutionBuild project that sits at the start of the dependency chain for my solution. This keeps things nice and tidy in my solution.

Conclusion

As you can see, this is a very simple use for T4 templates, yet it solves a reasonably common issue. In the past I may have addressed this issue of making sure the version files are up-to-date by adding to the release processes or creating a bespoke tool. Personally, I prefer the elegance of the T4-based approach and I hope that others find it just as useful as I do.

For more information on T4 templates, I found the following resources exceedingly useful: