I have now written twenty-eight little posts in the series called Writing a Habit Tracker! I’m pretty happy about that!

It’s not like we have implemented a whole lot of functionality. But we have discussed a whole bunch of stuff. I like to think of it as a kind of slow-cooking approach to programming, where we take the time to nerd out on details.

Here’s a summary of the posts so far:

  • In part one, I introduced the project, a habit tracker which I named hahabit, and proceeded to set up some software on my Digital Ocean Ubuntu machine: PostgreSQL and Java 19.
  • In part two, I created a Spring Boot app using Spring Initializr, and discussed what components to use.
  • In part three, I set up Testcontainers so that we could run the test suite.
  • In part four, I wrote some kind of functionality spec for hahabit and create an initial PostgreSQL schema for users with the help of ChatGPT.
  • In part five, I created a Spring Data JDBC repository for users, again using ChatGPT.
  • In part six, I discussed some details about Spring Data JDBC and records, improved on the user model and added a table and repository for habits.
  • In part seven, I added the table that tracks what I call the achievements, i.e. when you mark a habit as having been “done” for a specific day.
  • In part eight, I started configuring Spring MVC, decided I would serve web pages directly from Spring using Thymeleaf and set up a docker-compose file to run the service locally.
  • In part nine, I explored how the basic Spring Security setup works using curl, when it redirects to a form login and when it just challenges with Basic auth.
  • In part ten, I discussed how various options for how to make our user repository back Spring Security, and decided to go for the default Spring Security user schema instead for now, even though it’s weird.
  • In part eleven, I started on a little Thymeleaf HTML template for managing habits.
  • In part twelve, I discussed the difference between Spring’s @RestController and @Controller, complained about some weird API and wrote a simple controller for the habits page (with static data).
  • In part thirteen, I started reading the habits from the actual database repository, discussed Spring’s various types of dependency injection and figured out how to get the logged in user.
  • In part fourteen, I set up Spring Session JDBC so that sessions are stored in PostgreSQL and discussed how to make it more performant in the future without using Redis.
  • In part fifteen, I implemented form submission for adding new habits and made them show up on the page, explored various options for the handler, got very excited about simple functionality and briefly discussed replica lag.
  • In part sixteen, I explored how to use Thymeleaf conditionals, making it show whether a habit has been achieved or not for a certain day, and discussed the merits of using little domain languages compared to using the host language.
  • In part seventeen, I made it read the achievements for a specific date and looked at how to do that reasonably effectively and use a custom query in Spring Data JDBC.
  • In part eighteen, I figured out how to get the user’s time zone so that we can present the right date.
  • In part nineteen, I added a button with which we will soon be able to actually mark a habit as done, discussed the Post/Redirect/Get and what is and isn’t a nice way to do redirects in Spring MVC.
  • In part twenty, I made the button actually save to the repository and made sure we could only achieve our own habits – i.e., implementing access control, thus completing the functionality of the MVP habit tracker!
  • In part twenty-one, I explored how to build a JAR of our program that we will upload to our server, and fixed a couple of annoyances that surfaced during this process.
  • In part twenty-two, I set up my hahabit account on the server so I could upload things and made a script to build and upload the JAR.
  • In part twenty-three, I made the program run on the server and dealt a bit with various passwords, in PostgreSQL and the hahabit service itself.
  • In part twenty-four, I set up the program to continuously run on the server using systemd, and made my deploy script restart the service.
  • In part twenty-five, I finally exposed my application to the Internet by setting up my nginx server, and then making sure the traffic is protected by TLS using a Let’s Encrypt certificate.
  • In part twenty-six, I made the thing look a little less terrible by exploring various CSS frameworks.

I think part twenty-six marks for a good ending of Season One of “Writing a Habit Tracker”, and then there are a final two more “extra material” kind of posts:

  • In part twenty-seven, I get a Dependabot pull request, I update the third party dependencies and see if I can get rid of some bug workarounds and ask myself what the Spring Dependency Management plugin does for me.
  • In part twenty-eight, I get rid of the Spring Dependecy Management plugin.

I’m now planning to take a little break from this series, but hoping to continue writing a blog post every day. I have some other little things I’d like to focus on for a bit: a PR for Testcontainers and some improvements for this blog.

But after that, I hope to get back to the Habit Tracker. I might blog about improving the test suite, writing an API, writing a CLI client, visualizing the progress, writing an iOS client and enabling registration for other people. Who knows. Stay tuned!

Continue reading about my next little project, or jump straight ahead to part thirty of the habit tracker series.