NOTE: This is a cross-post that originally appeared in JavaScript January.
A while back while writing a React application that hit a REST endpoint, I wrote a little utility to build a properly encoded Uri path. At the time, I didn’t think much about it. It did the job and I moved on.
Months later, while working on another project, I needed something similar. I set out to find it and copy the code, but I couldn’t remember where I originally wrote it.
After a good deal of digging around — both on my local file system and on GitHub — I finally located it. I vowed that this would never happen again.
As I mentioned, the utility builds a Uri path. For example, given a resource of “user”
and a userId of “123”
, it might return a path to a specific user resource as follows: "/users/123"
.
The casual reader might be saying, “Why can’t you just concatenate the userId
to the end of the string "/users/"
and call it a day?” Maybe something like this.
const resource = 'users';const userId = '123';const path = `/${resource}/${userId}`;
That would be fine if you could swear on your mother’s grave that the name of the resource or the userId did not contain any characters that needed to be Uri encoded — not only today, but forever.
What if userId
values weren't just numbers, but handles, like "bob"
, "jim"
, etc. And what if there was a handle such as "this&that"
? This would produce a path of "/users/this&that"
which is an invalid URL.
Or what if, for example, the user entered their userId
as "?format=xml"
? path
would end up being "/users/?format=xml"
, which would likely return all users in XML format—not at all what we're expecting.
We could solve the Uri encoding issue in the previous example like this.
const resource = encodeURIComponent('users');const userId = encodeURIComponent('123');const path = `/${resource}/${userId}`;
Perfect! It works. But…that’s a lot of effort each time you want to generate a path.
This is why I came up with a utility that you hide away from your main logic and can re-use over and over. It incorporates the very useful, and often misunderstood, Array.reduce
method, which takes an array of values and reduces them down to a single value. In our case, we take an array of strings and values, and reduce them to a single string.
const buildUriPath = (strings, ...values) => (strings.reduce((partialUri, string, i) => (`${partialUri}${encodeURIComponent(values[i - 1])}${string}`)));
Use it as shown here and all that work of encoding each variable is abstracted away.
const resource = 'users';const userId = '123';const path = buildUriPath`/${resource}/${userId}`;
Briefly, it takes two arrays: strings
(the string constants ['/', '/', '']
) and values
(the templated string values that need to be encoded ['users', '123']
). It constructs and returns a single string "/users/123"
.
OK, that’s not entirely accurate. It is passed an array of strings, but what I’m calling an array of values, is actually a variable number of arguments. I’m using ES6 “rest” syntax to convert the arguments into an array of values.
When we reduce over strings, the zeroth element of the strings array is passed as partialUri and iteration starts with the first element, and so on.
There are no parentheses behind the call to buildUriPath
? What is this witchcraft? This is called a Tagged Template Literal. It’s a special form of the ES6 template literal that we used above, but it allows us to process it with a function.
Remember that we don’t call our code directly. It’s called by the JavaScript’s template literal function after parsing the template into separate elements.
Fans of styled-components are already familiar with this syntax. It’s a very powerful addition to the ES6 specification.
So now that I have a super-duper, handy-dandy buildUriPath
function, let’s share it with the world so that _everyone_can use it. But the thing is... Even if no one else wanted to use this, I still want to be able to use it myself, over-and-over easily. We can do this by making an npm package. I’m rather keen on the name build-uri-path
, so I hope that it’s available... and it is!
Because we wrote our utility in ES6, but the world still relies on ES5, we’ll use Babel to transpile it for us.
The complete source for everything described below can by found in my GitHub repo.
First create a GitHub account (if you don’t already have one) and then create a blank repo. Clone it to your local drive and change into the directory. Execute npm init
to create a skeleton package.json
. Be sure to set the entry point to lib/index.js
. This will add the following to your package.json
file, which instructs the consumer of your package what file to initially execute.
"main": "lib/index.js",
We will be writing our code in ES6, but the reality is that today’s browsers don’t fully support the syntax that we write in. Fortunately for us all, there is a utility called Babel that allows us to transpile ES6 source into ES5 distributable code.
To install Babel, execute the following in a terminal of your choice.
$ npm install -D babel-cli babel-preset-env
You’ll also need to create a .babelrc
file. This tells Babel how to transpile.
{"presets": ["env"]}
Open you favorite editor and create a src
folder with an index.js
file. This will be where we place our buildUriPath
function which will be exported as default
.
The entire source looks like this.
const buildUriPath = (strings, ...values) => (strings.reduce((partialUri, string, i) => (`${partialUri}${encodeURIComponent(values[i - 1])}${string}`)));
export default buildUriPath;
Run npm run build
and you should see Babel build a lib/index.js
file. Peek inside of this file if you want to see the ES5 transpiled code. Notice that it is substantially more verbose than it’s ES6 source. This is the syntactic sugar that ES6 brings to the table.
For larger packages consider using a packager such as rollup.js, but for our small package, publishing ES5 code should be just fine.
As a developer, you must prove to the world, and to yourself, that your code works under a wide variety of input values by writing lots of tests! They are also critical for regression.
Testing is an entire article unto itself. You can read more about JavaScript testing in the January 1st JavaScript January article “But really, what is a JavaScript test?”
You can, however, see that the tests pass for our package by running npm test
.
You should really consider setting up Continuous Integration (CI) such as CircleCI or Travis. Tests will be run automatically on each push to your code repo. This will help ensure code quality when you have multiple source code contributors.
Again, CI is beyond the scope of this article, but you can find a rather comprehensive explanation of setting up Travis CI here.
Setting up a linter, such as ESLint will also help you reduce errors by catching issues before you compile and test. You may want to consider using the popular ESLint configuration from Airbnb.
We’ll need to add a few scripts to our package.json
file to automate things. Add a build
command to allow us to manually transpile our code. Add a prepublishOnly
hook so that we can be sure that the tests pass and our code is transpiled automatically whenever we execute npm publish
.
Our scripts section looks like this.
"scripts": {"prepublishOnly": "npm test && npm run build","build": "babel src --out-dir lib --ignore '**/*.test.js'","test": "eslint src && jest"},
If you really want to impress, you can optionally add a TypeScript type definition file (the so-called d.ts
file) so that TypeScript users can have first class type support.
Just create the file types/build-uri-path.d.ts
, in our case. The file would look like this.
declare module "build-uri-path" {function buildEncodedUri(strings: string[],...values: string[]): string;export default buildEncodedUri;}
You also need to reference the file from within your package.json
file by adding the following line.
"types": "types/build-uri-path.d.ts",
See the TypeScript documentation for complete details.
All that’s left to do is publish to npm. But first you must be a user on the npm registry. If you don’t have an account, create one with npm adduser
. Once an npm user has been created, simply type npm publish
. The prepublish
script will run—which tests and builds—and if successful, you will have published your first npm package, my friend!
Now anyone can use your shiny new package. So why don’t we be the first? I’ll use CodeSandbox to install build-uri-path
and write a simple React app based on the Component Folder Pattern that uses it.
The app hits a REST endpoint to fetch some data. I’m using the Star Wars API as my back end. The front end React app allow the user to type in a resource and an ID. As these are coming directly from an untrusted source (i.e. the user), they need to be encoded. Good thing there’s an npm package that will do just that!
You can see the complete app, with source code, running on
.
You can see the complete app, with source code, running on
.The file loadData.js
imports our buildUriPath
function and uses it to build the path to send to the backend REST API.
import buildUriPath from 'build-uri-path';...const path = buildUriPath`/${resource}/${id}`;
You don’t have to write the next React to contribute a package to the Open Source community. If you see value in your work, then get it out there. Even if you are the only one who benefits, that’s reason enough.
But you may be surprised. Others may find it useful, and your meager little package could be the next open source sensation!
Thanks to Paul Bouzakis for helping with the TypeScript section and especially to Renato Selenica for fixing my mistaakes. ;)
I work at American Express and write for the American Express Engineering Blog. Check out my other works and the works of my talented co-workers at AmericanExpress.io. You can also follow me on Twitter.