Custom local domains for development
Due to the nature of my work, I write many web applications, often modifying multiple apps within a week or even a single day. I usually have a dozen development servers running at any given time (blame the new macbooks for having such amazing horsepower). I usually use two tech stacks, django and create-react-app. Both of these tools have amazing development servers, but they always default to the same ports. This makes it difficult to jump from one project to the other.
When spinning up a react app’s dev-server, it will try and use port 3000. If it’s unavailable, it will try 3001, 3002, 3003, etc. until it finds a free port. In the case of django, it will fail if 8000 is unavailable, in which case I have to manually specify localhost:8001. Depending on the order I start up my applications, they might be on different ports.
This can really screw up my browser’s URL autocompletion. It often suggests URLs from other projects, and doesn’t suggest URLs from the desired project if it was previously run on a different port.
My solution to this issue is what I’m calling custom local domains. Everytime I create a new project, I create a local domain name and a port for that project.
Desired outcome
I want my foo
application backend and frontend to be accessible via http://foo.localdev:3020
and http://api.foo.localdev:3021
.
Why 3020 and 3021? I semi-randomly chose it. The idea is not to conflict with common ports like 3000 or 8000, but also not to conflict with other projects that follow this same pattern and may already be using 3001, 3002, 3003, etc. Every new project I start will have an incremented port number.
Step 1: Enabling a local domain name
Right now, if I make a request to http://foo.localdev
, my computer will try and fail to find an IP using standard DNS resolution. Before I can tell our applications to use foo.localdev
, I need to add an entry in my /etc/hosts
file to map foo.localdev
to 127.0.0.1
. Since /etc/hosts
is a protected file, superuser privileges are required. I use this command to quickly add a new line:
echo "127.0.0.1 api.foo.localdev foo.localdev" | sudo tee -a /etc/hosts
Now both foo.localdev
and api.foo.localdev
will resolve to 127.0.0.1
, also known as localhost
.
Step 2: Run backend dev-server with new host (django-specific)
In the case of django, I like to create a run runserver.sh
script that will just run ./manage.py runserver
with a host argument.
echo "./manage.py runserver api.foo.localdev:3021" > runserver.sh
chmod +x runserver.sh
Now I can start my dev-server with ./runserver.sh
(rather than ./manage.py runserver
)
Step 3: Run frontend dev-server with new host (create-react-app)
Create-react-app already has hooks to support a custom host and port using environment variables. I just need to add HOST
and PORT
variables to my .env.development
file. I personally use .env.development.local
because I git-ignore the file and don’t want to force my team members to modify their /etc/hosts
files.
# .env.development
REACT_APP_ENV=dev
PORT=3020
HOST=foo.localdev
# usually, you'll also put your backend URL somewhere in your .env files
BACKEND_BASE_URL="http://api.foo.localdev:3021"
Generalizing to other environments
If you’re using another stack with its own dev-server, chances are you can also use a custom host and port. As long as your /etc/hosts
maps your custom local domains to 127.0.0.1, it should work just fine.
Next steps?
In order to give each project a unique port, I have to keep a mental counter of which port I’m currently on. It can be really annoying to remember which port I should use for a new project.
Wouldn’t it be nice to not need a port at all and just use foo.localdev
? This is certainly possible, but relatively complicated because /etc/hosts
cannot map a host to a host:port
pair. We could install additional dependencies like nginx to map hosts to individual ports. This has the added benefit of preventing us from accidentally accessing one application using the correct port but another project’s host (That’s right, if we have a dozen running services running this way, we can access any one of them using localhost any other service’s host)