At Eager, we use a microservices architecture, where we split our backend into different services and servers, which makes it easier to isolate bugs and speed up the development cycle. And of course whenever we update a piece of our backend, we have to make sure that it works with existing frontend as well as the other backend services that we haven't modified. There are a few ways to do this.
The easiest and most obvious way is to deploy the frontend locally, and update the links to the deployed services (e.g. api.eager.io) have them point to the locally deployed backend (e.g. localhost:3001).
But when you have multiple backend services, with different links to each other, and both your frontend and your backend projects have multiple places where these links are specified, changing back and forth betwen
localhost:port-num-x for each service gets both time consuming and prone to errors. What if you accidentally deploy your client side code while it's still making requests to localhost for resources?
You could set environment variables whenever you needed to use a local service over an external service, but this can cause errors when you forget to set (or not set) a flag when running your code in production.
There's another way, though, and it doesn't require you to change your code or even the command or environment variables you use to run your program locally.
Rerouting outbound traffic
By using a tool like vee and modifying your hosts file, you can redirect traffic from your local frontend to your local backend service. Or from your local backend service to other running local backend services. Vee basically takes the traffic redirected to localhost and allows you to redirect it to wherever you'd like, based on the original url it was redirected from.
When I work on eager development, I do something like this for my hosts file:
# Eager Backend Development 127.0.0.1 api.eager.io 127.0.0.1 hooks.eager.io 127.0.0.1 service-name-x.eager.io
And something like this for my vee file:
name: "GLOBAL-VEE" routes: ".*api.eager.io/(.*)": "http://localhost:3001$1" ".*hooks.eager.io/(.*)": "http://localhost:3002$1" ".*service-name-x.eager.io/(.*)": "http://localhost:port-num-x$1"
Now, you can run vee to redirect outbound requests from local clients and services and send them to the instances of those services running locally. Vee supports regex, so what we're doing here is copying the first capture group from the left side (the
(.*)) and pasting it to the right side (the
$1). You can use multiple capture groups and more complicated regex if needed, but something similar to this should be enough for most configs.
However, there are some minor problems that we'll have to fix if we're using SSL.
Getting around x509 errors
If your links start with
https://, however, we'll have to do a bit more. Making SSL connections requires a valid SSL certificate. Otherwise, you'll get errors similar to
ERR_INSECURE_CONNECTION in your browser console when testing frontend code, and
x509 certificate signed by unknown authority when testing backend code.
This is expected for https connections. If we redirect outbound https traffic to our local server without a valid certificate for the domain we're redirecting from, the request should fail. Otherwise we'd open ourselves up to man-in-the-middle attacks from servers impersonating the original domain. (Of course, as always, we're still vulnerable to MITM attacks when sending requests over http.)
Your deployed server that receives those SSL connections serves a CA signed cert in order to for clients using SSL to accept connections with it. we could technically use that certificate with your local vee config, but we probably shouldn't toss that around so lightly.
You can temporarily get around this in the frontend by navigating to the pages being redirected to localhost and making your browser trust them, but this will only persist as long as your session. And even that override is not always easily done when making requests from backend services which (rightfully) check for appropriate SSL certificates.
Don't fret, though! There's only one simple step left to making your local computer interact with your development code just like it was pushed to production!
git push origin master -f
Just kidding, not exactly as if it was pushed to production, the next step is actually:
Creating a self signed certificate
By creating a self signed certificate and trusting it for all types of connections, we can make our computer think that our locally running servers have had their certificates signed by legitimate CA cert authorities and are the actual servers that we're redirecting traffic from. (If you need to redirect wildcard subdomains, skip to the next step. If you need to redirect multiple root domains, skip to the section on creating a SAN cert)
First, go to your cli and type in:
openssl genrsa -out self-signed-cert.key 2048 openssl req -new -key self-signed-cert.key -out self-signed-cert.csr
Then, type in whatever information you want, making sure to put your domain name that you're signing for under common name. For example, if I was testing an eager Eager backend service, I'd put
*.eager.io, since this field accepts wildcards. Notice, however, that while this field accepts wildcards, your
hosts file does not, and you will either have to write out all of the subdomains you want to redirect individually, or use a local DNS service. Next, run:
openssl x509 -req -days 3650 -in self-signed-cert.csr -signkey self-signed-cert.key -out self-signed-cert.crt
.crt file is SSL certificate, and the
.key file is your SSL private key. Use them in vee by giving the
--ssl-cert flags their file location.
Now, add the
.crt file to your keychain on OSX using:
sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain ~/FILE_PATH_TO/self-signed-cert.crt
or for Linux:
sudo cp ~/FILE_PATH_TO/self-signed-cert.crt /usr/local/share/ca-certificates/self-signed-cert.crt sudo update-ca-certificates
or for Windows:
certutil -addstore -f "ROOT" ~/FILE_PATH_TO/self-signed-cert.crt
Congrats! Now you should have no x509 errors! If you're still having errors, it might be because you didn't restart your service or refresh your browser after you self signed your key (which means it hasn't cleared the invalid key from its cache). In most cases, you can skip the next two steps, you're done!
Wildcard Subdomain Redirection
In the case of wildcards in our subdomains, we've unfortunately hit a point where we can't always just redirect everything with our hosts file. Our hosts file is a bit too simple to understand wildcards. You can manually input every full url you could get, but that gets a bit tedious, and if you're generating urls with infinite subdomain possibilities on the fly, it's impossible. While we can easily make a SSL cert that validates all of these subdomains, automatically redirecting outbound requests is harder.
Fortunately, there are tools for running local DNS servers that do exactly what you want. We use dnsmasq for a nice tool to use in those cases.
After installing dnsmasq, using
brew install dnsmasq or however else you'd rather do it, let's set up its config file. In OSX, this is in
/usr/local/etc/dnsmasq.conf. You can create a blank file, or copy the file from your dnsmasq install folder. (If using homebrew, just type
brew list dnsmasq and find the
Next, you'll want to add something similar to these lines to your dnsmasq file. If you copied the
.conf.example file, these are supposed to go around line 80:
# Eager Backend Development address=/api.eager.io/127.0.0.1 address=/hooks.eager.io/127.0.0.1 address=/alternate.domain/127.0.0.1
What this does is it redirects all outbound connections going to
*.alternate.domain, back to localhost. You can even redirect all connections from a specific TLD, e.g.
.io, by writing
address=/io/127.0.0.1. You can now also delete the lines you added in your
hosts file earlier. You should also add a line in your vee config for
Now, start (or restart) dnsmasq. Using brew, this would be
sudo brew services restart dnsmasq. Then, add localhost to be your primary DNS server, either through CLI or through the network preferences gui. Then flush your DNS cache. For the latest version of OSX, this is
sudo killall -HUP mDNSResponder. You'll have to restart dnsmasq and flush your DNS cache every time you want to apply changes from your
dnsmasq.conf file. You can stop dnsmasq, and you will still be able to resolve addresses, since your machine will check the next listed DNS server in the event that the previous one doesn't resolve an ip address.
Here are some shell aliases I've made for these and other relevant commands:
alias gdns="sudo networksetup -setdnsservers Wi-Fi 22.214.171.124 126.96.36.199" alias localdns="sudo networksetup -setdnsservers Wi-Fi 127.0.0.1 188.8.131.52 184.108.40.206" alias catdns="cat /etc/resolv.conf" alias flushdns="sudo killall -HUP mDNSResponder" alias restartdns="sudo brew services restart dnsmasq && flushdns" alias startdns="sudo brew services start dnsmasq" alias stopdns="sudo brew services stop dnsmasq"
(220.127.116.11 and 18.104.22.168 are google's primary and secondary public DNS servers)
Now, you might have remembered that we can only put one common name per SSL cert. That's true, but at the moment, vee only supports serving one certificate regardless of redirected domain name. How do we fix it?
Creating a SAN SSL Cert
The SAN in SAN SSL cert stands for Subject Alternative Name, which means that you can have one SAN cert that covers multiple domains, subdomains, and raw ip addresses, though the latter isn't recommended. This means that we can have vee serve a self signed SAN SSL cert that validates all of the services we could possibly want. We can even have completely unrelated domains on the same SAN cert, such as
gist.github.com, and they can all use wildcards!
To do this, let's create a configuration file to store all of our alternate names. Mine looks something like this and is named
[req] distinguished_name = req_distinguished_name req_extensions = v3_req [req_distinguished_name] countryName = Country Name (2 letter code) countryName_default = EG stateOrProvinceName = State or Province Name (full name) stateOrProvinceName_default = Eager Development localityName = Locality Name (eg, city) localityName_default = Eager Development organizationalUnitName = Organizational Unit Name (eg, section) organizationalUnitName_default = Eager Development commonName = Common Name commonName_default = *.eager.io [ v3_req ] basicConstraints = CA:TRUE keyUsage = nonRepudiation, digitalSignature, keyEncipherment, dataEncipherment, keyAgreement, keyCertSign, cRLSign subjectAltName = @alt_names [alt_names] DNS.1 = *.eager.io DNS.2 = eager.io DNS.3 = *.alternate.domain DNS.4 = alternate.domain
CA:TRUE, which makes it a CA cert, and that all of the valid keyUsages are set.)
Note that validating wildcard subdomains does not validate the root domain, so if we're validating
*.alternate.domain, the cert won't cover
alternate.domain unless we explicitly tell it to.
Now, we create they key and cert. Refer back to the section on creating a self signed certificate for clarification. Since we have a
.cnf file to store our options, we can use these commands for creating the key instead. Assuming we've named the file
openssl genrsa -out self-signed-cert.key 2048 openssl req -new -key self-signed-cert.key -out self-signed-cert.csr -config ssl-conf.cnf openssl x509 -req -days 3650 -in self-signed-cert.csr -signkey self-signed-cert.key -out self-signed-cert.crt -extensions v3_req -extfile ssl-conf.cnf
Add it as a trusted certificate as shown in the section on creating a self signed cert, and you're done!
Congrats, you made it to the end! If you've followed all of the steps, you're all set up to do some quality backend development with no hassle. The best part is, if you're using dnsmasq, you can simply stop the dnsmasq service and flush your DNS cache, and you can immediately access your production servers, then start dnsmasq and flush your cache to have your outbound connections once again redirected to localhost.