Portrait of David Findley

Family Recipes

Project URL: https://findley.recipes
Source Code: https://gitlab.com/findley-recipes
Technologies: JavaScript, Node.js, MongoDB, React, HTML, CSS

This project was a Christmas gift for my parents in 2016. My dad has always been computer savvy, and a meticulous record keeper. He used a Microsoft DOS program called MealMaster to keep track of family recipes. The original website is gone, but thankfully you can experience it in all of its glory on the web archive.

When he upgraded his home computer from Windows XP to Windows 7, MealMaster no longer ran natively. Luckily we found a work around using DOSBox, but I had a feeling that the days of being able to practically use MealMaster where numbered. I started planning to build a simple recipes web app and more importantly, to migrate the MealMaster recipes into it.

Screenshots🔗

Migration🔗

MealMaster used an interesting text file format for its database which is also human friendly. It's been a while since I worked on this project, but I do remember finding a specification for the MM format. Searching the internet today yields a post from Jan Wedekind who wrote up formal documentation for the format. This is from 2020, so it wasn't what I used when I wrote my converter, but I'm grateful anyway for his work.

Here's an example of what one recipe looks like:

MMMMM----- Recipe via Meal-Master (tm) v8.00
 
      Title: HOLIDAY BAR COOKIES
 Categories: Desserts, Cookies
      Yield: 25 servings
 
      2 x  EGGS
      1 x  DARK BROWN SUGAR (1 BOX)
  1 1/2 c  FLOUR
      1 t  VANILLA
      1 c  BUTTER - MELTED
      1 c  PECANS - CHOPPED
      1 x  SALT - DASH
      1 x  POWERED SUGAR
 
  BEAT EGGS AND ADD BROWN SUGAR.  SIFT THE FLOUR WITH THE BAKING POWDER
  AND SALT.  ADD TO MIXTURE MELTED BUTTER AND VANILLA THEN MIX WELL.
  STIR IN PECANS AND BAKE IN A GREASED 9 X 13 PAN FOR 45 MIN. AT 325
  DEGREES.  COOL BEORE CUTTING INTO BARS.  SPRINKLE ON POWDERED SUGAR.
 
MMMMM

The backup file, is 32,000 lines of this. Unfortunately a lot of the original text entry was in all uppercase. I'm not sure if this was just how an old version of the program worked, or if my dad entered them like that.

Since I selected MongoDB for this project, I had to write a script which could read this backup file and convert the recipes into JSON. I wrote import.js to handle this task. It was pretty quick and dirty, and it took quite a bit of iteration and post-processing fixes to arrive at a good conversion that was ready for Mongo. Here's what the recipe looks like after conversion:

{
    "name": "HOLIDAY BAR COOKIES",
    "author": "Unknown",
    "category": "Desserts",
    "tags": [
        "Cookies"
    ],
    "yield": 25,
    "ingredients": [
        {
            "list": [
                {
                    "qty": "2",
                    "unit": "x",
                    "name": "EGGS"
                },
                {
                    "qty": "1",
                    "unit": "x",
                    "name": "DARK BROWN SUGAR (1 BOX)"
                },
                {
                    "qty": "1 1/2",
                    "unit": "c",
                    "name": "FLOUR"
                },
                {
                    "qty": "1",
                    "unit": "t",
                    "name": "VANILLA"
                },
                {
                    "qty": "1",
                    "unit": "c",
                    "name": "BUTTER - MELTED"
                },
                {
                    "qty": "1",
                    "unit": "c",
                    "name": "PECANS - CHOPPED"
                },
                {
                    "qty": "1",
                    "unit": "x",
                    "name": "SALT - DASH"
                },
                {
                    "qty": "1",
                    "unit": "x",
                    "name": "POWERED SUGAR"
                }
            ]
        }
    ],
    "instructions": [
        {
            "content": "BEAT EGGS AND ADD BROWN SUGAR.  SIFT THE FLOUR WITH THE BAKING POWDER\nAND SALT.  ADD TO MIXTURE MELTED BUTTER AND VANILLA THEN MIX WELL.\nSTIR IN PECANS AND BAKE IN A GREASED 9 X 13 PAN FOR 45 MIN. AT 325\nDEGREES.  COOL BEORE CUTTING INTO BARS.  SPRINKLE ON POWDERED SUGAR."
        }
    ]
}

Authentication🔗

I didn't want this app to just be a static backup from MealMaster, so it needed to allow for entry of new recipes. The problem is that MealMaster was a program you ran on your own computer, but findley.recipes is a public web app. This makes it really easy to share recipes, but it also means that we have to have some sort of authentication to prevent unauthorized users from adding or vandalizing the content.

I never really intended this app to be multi-user, but it has pretty much all the infrastructure to handle it. The database stores a single "admin" user record with a hashed and salted password. There's an endpoint for authentication which produces a JWT, and there's an endpoint to change the password too.

From the UI, the login page only asks for the password (since there's only one user).

Once logged in, the user can edit and add new recipes!

Virtual Scrolling🔗

I spent an unbelievable amount of time agonizing over virtual scrolling of the recipe list. I really wanted it to feel like the whole list was just loaded up-front and you could scroll through it seamlessly. It turns out this is non-trivial, and I don't think I would ever recommend building in virtual scrolling into a web app. There's a lot of ways that things can go wrong and result in a "janky" experience for the user. I think a basic paged system, or infinity scroll (which loads more when you get near the bottom) are much better options.

Ultimately the search at the top of the app doesn't even page at all (it just shows the 30 best matches). This can be problematic since some more generic searches, like "dessert" will produce much more than 30 results, and there's not a way to browse all of them. So I did end up adding a /browse page which uses the best implementation of virtual scrolling that I could muster. I'm actually proud of how well it works (try it out!). You can grab the scroll bar and instantly drag it to the center of the list no problem. And there's minimal "jank" when quickly scrolling through the list. This works so well for two reasons: 1) The list endpoint is super fast. 2) The list items are extremely homogeneous.