Routing with Polymer and Express

Posted by wmginsberg on nov 17th, 2016 | 10 minute read

As a library of Web Components, Polymer is back end agnostic, working entirely on the client side, or front end, of an application. This means that you can use whichever hosting service, database, or server technology that you’d like to handle the server-side, or back end, of your application.

The flexibility of Web Components is liberating, because it enables developers to build front end UI the same way, regardless of which back end technologies they’re using. If you’ve developed a beautiful component-based UI and you’re looking to hook up back end logic, you’ve come to the right place. Here, I’ll give you a quick run-down of how to add server-side and client-side routing for your web app with Polymer and Express. Afterward, we’ll talk a bit about applying the PRPL pattern, an optimization strategy for loading content in web applications.

Note: this is not a step-by-step codelab. However, you can follow along by checking out the corresponding ✨github repo✨.

---

I. What is Express?

One of the most popular server-side web frameworks is Express. Brought to you by Node.js, Express is incredibly lightweight and easy to use. With Express you can have a server up and running for your web or mobile application in seconds. In fact, we love Express so much that we built our polymer serve CLI tool with it! You might want to add Express to your application for server-side routing, templating, or middleware integration.

With Polymer in the front and Express in the back, you can set up routing for your application. Routing refers to the way a web app can navigate from page to page via URLs and HTTP requests.

(!) Before you start, make sure you’ve installed Node and Express to process your server-side code, and Polymer and Bower to process your client-side code.

II. Setting Up Your Server

You’ll want to start with a basic Express app that serves a static client folder with an index.html file. This is a high-level sketch of my file structure.

poly-express/
├── bower.json
├── build/
│ ├── bundled/
│ └── unbundled/
├── client/
│ ├── bower_components/
│ ├── my-app.html
│ ├── my-icons.html
│ ├── my-view1.html
│ ├── my-view2.html
│ └── my-view3.html
├── index.html
├── manifest.json
├── node_modules/
├── polymer.json
└── server.js

If you want a deeper look, you can check out my poly-express github repo to reference the directory I used in greater detail. The file that houses that Express app server could be called server.js, and would look something like this:

// Node.js notation for importing packages
var express = require('express');

// Spin up a server
var app = express();

// Serve static files from the main build directory
app.use(express.static(__dirname + '/build/bundled'));

// Render index.html on the main page, specify the root
app.get('/', function(req, res){
  res.sendFile("index.html", {root: '.'});
});

// Tell the app to listen for requests on port 3000
app.listen(3000, function () {
  console.log('Example app listening on port 3000!');
});

The only thing you need to do differently to smartly connect an Express server to your Polymer project is ensure that your bower components and custom elements are all being served out of the same client-side public build directory.

You can automatically direct the target download directory for bower_component installation by editing your bower config, or .bowerrc, file. Your .bowerrc file is a config file for your bower package that’s written in JSON. Just add this one small code snippet to the file, and you’re good to go. If you already have a bower_components folder, manually move it into the client folder first.

{
 "directory": "client/bower_components"
}

You can test this by installing an element via bower and checking the directory it downloads into. If it creates a bower_components/ folder in client/, or installs into your already-existing client/bower_components/, then it’s working!

III. Adding Routes to the Client

What you have now is the groundwork for a single page app connected to an Express server, which is great, but we came here to add routes, so we’re only halfway there.

There is a Polymer element that can be used to construct modularized front-end routing, which is appropriately named <app-route>. We’ve assembled a bunch of helpful app elements like <app-route> in the App Toolbox’s App Template. I’ll be using the App Template here, since it will install a Polymer app with client-side routing set up already.

If you want to add client-side routing to your existing Polymer project, your best bet would be to use <app-route> and <iron-pages> together, just like we’ve done in the App Template. If you're using the App Template, this is all done for you automatically, but if you're working from scratch you'll need to include these elements manually. You can use the my-app.html file from the reference repo for guidance, or check out our documentation. As you can see from the basic <iron-pages> example below, adding just one line of HTML adds a new route.

<iron-pages selected="0">
 <div>One</div>
 <div>Two</div>
 <div>Three</div>
</iron-pages>


<script>
 document.addEventListener('click', function(e) {
  var pages = document.querySelector('iron-pages');
  pages.selectNext();
 });
</script>

Attach an attr-for-selected to your inner pages to connect them, and include an iron selector with anchor tags to create navigation links.

(!) PSA/friendly reminder to make sure that the imports in your index.html file have absolute paths, meaning each import path has a slash (“/”) at the beginning, since you never know where the file will be accessed from.

When you’ve set everything up, and have run polymer build to create your build/bundled directory, you can run node server.js, and see that you can click through and navigate from page to page. This means that client-side routing has been set up. This will make navigating around your app much quicker, and if you have a service worker set up, you have offline routing as well. Woohoo!

So, now we have client-side routing and a server, but now try accessing one of your new routes directly from your browser bar, or via a link. If you’re using the App Template try typing in localhost:8080/view3 and hitting enter, otherwise type in your own routes. You’ll notice that the page associated with view3 does not appear, and you get an ugly "Cannot GET /view3" message. This will also happen if you’re on one of your routed URLs and try to refresh your current page. That’s because we’ve made an HTTP request to the server for “/view3” and the server has not yet been taught what that means. Computers are smart, but they’re not mindreaders.. yet.

IV. Adding Routes to the Server

Our current server.js code has a listener to serve index.html from the main route, or slash character ('/'), but we want our server to know to serve index.html from any route. To do this, we can simply replace our slash character ('/') with a wildcard character (‘*’). The wildcard encompasses all possible strings that can follow the slash, so the server will serve the index.html page from any route. Now, we do not need to explicitly add individual routes. This will also ensure that we’ll never need to fallback on a 404 page, since any page will be caught and redirected to index.html. Implementation here:

...

// Render index.html on the main page
app.get('*', function(req, res){
  res.sendFile("index.html", {root: '.'});
});

...

You might be wondering why I left “index.html” as the file to render for every route. That’s because we are designing a single page app, where the client handles all of the routes. My <app-route> and <iron-pages> components are located in my top-level element, <my-app> which is housed in index.html. This code intercepts any GET requests for routes (i.e.: “/view3”, “/view1”) that are sent to the server, processes them, and redirects the page to index.html to make the appropriate routing call. By handling all of this in the client, we’ve built a quicker app, saving an entire round trip server visit on each request! Check out the diagram below for an illustration of how a request from the client bounces to the server, is processed, returns to the client, and is routed to the corresponding view.

A page request's bold journey from client to server, to client again.


Now that you've set your server up to process all routes, try accessing your pages directly from the browser again. If it doesn’t work right away, make sure you’ve killed and restarted your node server in the command line, since you’ve edited server-side code. This will refresh the code that you’re running, and read your new routing rules properly.

There you have it - server-side routing with Polymer and Express! This routing pattern can handle direct requests for various URLs, super quick on-site navigation, and (if you have a service worker set up) offline page-to-page navigation. Feel free to send questions over on Twitter, or as issues on the repo!

V. Some Thoughts on optimizing with the PRPL Pattern

Because we started off with the App Template, we automatically get a service worker and efficient rendering when we run polymer build. However, our App doesn’t fully use our PRPL pattern, since we send down the entire app for every route. This works fine for a small app, but for a giant app with a couple dozen routes, or even fewer heavy routes, you'd want to smartly bundle your app routes.

We are working on a something that will make this more straightforward, and will report back on those soon. In the interim, I’ll give you an overview of what we do and don’t have with the PRPL pattern.