We all know that naming is one of the hardest steps in computer science. Not only should naming conventions be understandable for your user, they can impact the functionality of your app as well. For me, figuring out how to structure your API routes falls into that category. Consider the following examples:
- https://app.example.com, with an entry in localStorage customerName=CUSTOMER
In each case, it’s clear to the application who the CUSTOMER using the product is. You can find many SaaS applications using each of these patterns and some SaaS applications that use multiple at a time. While they all seem similar, they do end up having subtle differences that affects local testing, UI/UX, and more. Below we’ll look at some of the pros and cons of each approach.
Subdomain per customer
By using a subdomain for your customer, you get a few advantages. You can, for example, set up different DNS entries for different customers. If you want to route all traffic for that one big customer to their own infrastructure, you can do that at the DNS level. If you want to have traffic for different customers go to different geographies, you can do that at the DNS level too.
While it’s possible to do these actions with a path based approach, but you’d likely need to do it with a load balancer instead.
One of the biggest drawbacks of this approach is the local development story. You cannot have a subdomain of localhost (e.g. http://customer.localhost:3000), so you’ll need to figure something else out. We’ve seen people edit their /etc/hosts file to create custom domains locally, but whatever you do here is likely going to be more work than just putting the customer name in the path.
Ultimately, this approach is probably more trouble than it’s worth, and you should only use it if you need to and are willing to put in some extra work.
Path per customer
This approach is what we’d recommend. It’s similar to the subdomain per customer approach, but has the added advantage of playing nicely with just about any local/test development environment.
Every major backend and frontend framework has good support for path parameters, so it’ll be easy to get the customer’s name/ID. For example, in Express, this is as simple as:
Query parameter per customer
There aren’t too many differences between this case and the Path per customer case. A subtle difference here is basically just a perception difference. Using a query parameter feels less official than a path parameter.
There are also some cases where some actions (like submitting a form) can modify the existing query parameters - so to be safe, we’d recommend using the path approach over this.
LocalStorage entry per customer
https://app.example.com, with an entry in localStorage customerName=CUSTOMER
In all of our examples so far, the URL contained some indication of who the customer is. If the customer is an “organization” and users can be in multiple organizations, there’s a nice advantage here of allowing the user to explicitly state which customer they are currently using.
However, let’s take an example like Notion:
Notion lets you select an account, but that account doesn’t show up in the URL. This can be useful if your content isn’t necessarily tied to a single customer.
In Notion, I can share a page externally with anyone, so it might be strange for them to see https://notion.so/acmeinc/sharedpage if they don’t have access to Acme Inc. in the first place. The downside of this approach is when a user first lands on your site, you’ll need to figure out which account they are accessing.
If you have multiple Google accounts, you’ve probably been inconvenienced at least once when you opened a Google doc only to realize that your current account doesn’t have access and you need to switch accounts.
There are many ways to structure your URLs for your customers. You can explicitly reference the customer in the URL or you can manage that separately. You can put the customers name in the path, a subdomain, or a query parameter, each of which have different pros and cons.
We recommend you start with using a path parameter as it has the best development story without any major drawback and only switch if you have a specific reason to. If you are looking for how you might implement that in React, check out our post were we use React-Router to set that up.