Setting up the development environment

If I were to be pitched as a software product, no one would tout my consistency. I like to change things up, and new adventures excite me more than stability. It's odd then that I'm also really anxious, but that's a story for another time. As with most things in my life, my development setup is also affected by my inconsistency. Each project I set up is different in one way or the other, incorporating ideas I then have about what the ideal setup looks like. In this post I'll outline the way I've set it up for reread.

When I say development setup, I don’t mean my IDE etc or laptop. Anything that remains constant between various projects is excluded from this post. What does it include then? Anything of note from source code repository structure, build tooling, tooling used to run, reload, serve the project etc.

I try to prioritize simplicity and efficiency when setting up a development setup. Simplicity in new projects is really easy, but it’s not something to take for granted. As your project grows, the cute little tamagochi-like folder with 3 files will soon become an ogre-octopus with tentacles going in every direction. But that doesn’t mean that you don’t cultivate it. A groomed orge-octopus with trimmed tentacles is a good ogre-octopus.

The reread structure

Reread will be comprised of two parts: a single page application written in Typescript using NextJS, and an API server written in Go.

So, right from the outset, it makes sense to two folders, one for each big part of the app. Whether to have a single repo for both server and SPA, or separate repos is a usual question here. I’ve tried both before, and both have tradeoffs. I currently like the monorepo approach with multiple folders for reread because there’s a single engineer — yours truly. I can also publish / deploy fullstack changes in a single PR, and it’s less overhead. So, with the original intention of being simple and efficient, reread is a monorepo for now.

The structure looks like so:

reread.to
├── app
    └──  package.json
├── server
    ├── Makefile
    ├── main.go
    └── Dockerfile
├── install-deps.sh
└── Makefile

Building

In order to build, I decided to use makefiles. They’re simple, available everywhere, and easy to maintain (usually, they can also grow to be a tentacle monster). They aren’t at their most powerful when using with go, or NextJS, but they serve their purpose for now.

I use a makefile per project: one for the backend, and another for the frontend. Then there’s a global makefile, placed at the root of the project that calls the per-project makefiles to run fullstack build operations conveniently.

Here's how the makefiles look like:

Dependencies

Another thing I’ve recently started to do with my projects is to keep a ‘global install script’. Each tool you use has a dependency that either comes pre-installed, or you manually installed before you used it. Examples of these are the go language tooling, NodeJS, even makefiles.

In a lot of projects I’ve seen, these dependencies are written down in a readme that the developer is expected to read and run commands to get their machine up to par before the project can be run or built. I think that’s a less than ideal experience. Instead, I’ve started to maintain an install.sh shell file at the project root. Each time I install a new tool for the project, I just add the commands to the install script and run that. This ensures most of the dependencies of the project are easy to install. For reread, the current install script looks like this:

It has accumulated some extras as I've started experimenting with secrets management, etc, but I don't see it as a problem for now.

Docker

I want a reproducible environment for running the backend and the easiest way for me to do that is to use docker.

The frontend is not run inside a docker container. In my past projects I ran into problems with where to run npm install, on my machine or within the docker container because packages like Bcrypt etc need to be built and it’s a bit of a mess. Again, solvable, but I don’t much benefit from putting it in a container, and all the pain, so I ignore it.

For the backend container I’m using something called Air which deals with all the meds around file watching, rebuilding and restarting my backend server. It’s been a lifesaver and works amazingly.

Here is how my server dockerfile looks like:

For development I use docker compose because I know it and it does what you want without having too complex a syntax. I don’t have a database yet, so I’ll cover the docker compose in more detail once I actually have two containers.

My working setup

If you look at my root Makefile linked above, you’ll see a stacked command. That basically does two things: starts docker compose but doesn’t wait for it to finish, and starts up the front end server.

stacked: 
    $(MAKE) up& $(MAKE) appdev

The weird syntax aside, it basically allows me to stream both the server output as well as the frontend output while using a single command and a single terminal to run both processes.

Both commands independently watch their respective code, and reload. NextJS takes care of auto-refreshing the browser, and Air auto-compiles and restarts the server. This way, I just focus on development and everything else takes care of its own.

Next up, I’ll dive into setting up NextJS and the Go server.