Get Notified with HadCoffee Launches

HadCoffee will be a mobile web app to help you discover, save and share the best specialty coffee. Launching late 2021 with a Brisbane focus at first.

If you want to try out the beta version when it’s ready drop your details here. I won’t spam you. (Privacy Policy)

Keep Updated

Server Woahs

I’m getting ready for a beta launch and have setup a production VPS. It’s oddly slower than the staging server despite being on the same (or very similar) plan from the same provider.

Both are 1 vCPU, 1GB RAM, NVMe SSD, Ubuntu 20.04 and PHP 7.4, but the older staging server is faster everywhere.

The new server was configured by Forge, instead of me doing it manually.

It can build and deploy the app in ~75 seconds instead of ~120. The queries run faster and it has lower memory usage.

I’ve also now benchmarked it with Sysbench, and again for both CPU and disk the staging server is faster.

  • Disk writing 8GB of files: Staging ~21% faster
  • CPU Prime numbers, 10K limit: Staging ~41% faster
  • CPU Prime numbers, 20K limit: Staging ~38% faster
  • App build and deployment: Staging 38% faster

Update: Having tested on a 2 vCPU server, my old Ubuntu 16 box, and a Vultr High CPU plan it seems the slower prod server is closer to normal, and my old staging one is an anomaly.

Cafe Search Frontend Data Flow

The home screen will let you search for cafes, either by location, or by cafe name (or the combination of the two).

Today I made good progress with the frontend flow of data to support this feature. The app uses Vuex for state storage. This feature has three different Vue components accessing related data to manage the users search.

The quick location search will use the browser geolocation feature as a default, and that data will flow through to the search form via Vuex. If the user is searching for a cafe by name that content will also go through Vuex for global access.

When the search event is triggered the results component can access this state, and send the API request to the server and build out the result set.

The event listening process was a little tricky, as state properties are accessed via getters, and so the normal method of capturing events and passing to component properties won’t work. A combination of Vuex getters, computed properties and watchers lets the result component listen for the search action to do its work.

I can now work on the backend logic for the API endpoint and build out the results data.

I’m a little concerned that that work could get heavy as this is almost a mini-SPA now. Possibly Livewire would have been a good solution, but I’m not going to introduce new architecture at this point in the project development.

I may have to consider how to cache the search result if the user navigates away and comes back. I’d prefer to avoid having to refetch from the server as that delay is annoying when it’s data you have already accessed. Worst case scenario I guess is to capture search criteria in localStorage, and cache the result server side so the response comes in <100ms.

Rating by Type of Coffee

Not all cafes will make white, espresso or filter coffees equally, or equally well. Some might have a focus on espresso and bit a little average for the whites, or vice versa. Rather than combining all their ratings and losing that granularity I will let users specify what sort of drink they had when rating a coffee.

screenshot of coffee type UI
WIP UI for selecting drink types

I think this will be important to help give better recommendations. A cafe might do stellar cappuccinos, but if they’re ordinary at a Long Black and that’s what you’re going to order the Cap won’t help you!

The feature does add quite a bit of complexity to the development though. I need to start tracking ratings separately for the drink categories, as well as in aggregate. The scoring algorithm will also ideally weight itself towards a users history and preferences too.

It also has frontend implications with extra UI, state management and icon design requirements. The image above is still, but I am working on building subtly animated SVG icons for the drink types to enhance the UX of choosing them. I’ve got a nice little stepped animation to show the selection action.

SVG

I love the flexibility of images-as-code and being able to add simple animation effects with CSS. I am a little concerned that bundling the SVG icons into Vue components will be adding more weight to the final JS build.

I think referencing a static SVG as an image will prevent me from being able to style and animate individual shapes with CSS. It needs to be an embedded object. When I’m already deep in a Vue component structure that means the icon also needs to be a Vue component (as opposed to server side injected SVG code).

In my defence though, I think most of the audience who are into visiting specialty coffee cafes for entertainment probably err towards having high end phones that are capable of dealing with the load. The JS will be cacheable and probably even service worker cached in the future, so it’s more a CPU/parsing issue.

Backend

Most of the backend adaptations are still to be done though, and the actual search weighting will happen later as I build that feature.

Glad to be Moving to Vuex State Management

The growing amount of data stored for a rated coffee would get difficult to manage within a single parent component. I’m glad I’ve started the work of using Vuex to control the global state. Passing this many props and events up and down the component chain would be quite messy.

Overall Progress

So much of the project framework is complete, but there are a few key features left to build.

  • Drink Type management described here
  • Reverse geocoding and caching so the app can refer to your suburb/city and not just Lat/Long
  • The main cafe search feature, using that geographic information and drink preferences
  • Ability for users to add missing cafes at the time they are entering a review. (This can use some of the existing backend code I have already written from an admin perspective)
  • Service Worker / PWA bundling. This will be very minimal for now. Home screen, and basic asset caching, but no real offline support.
  • Performance and A11Y review

Images

Coded a basic cafe banner image handler today.

  • Resizes to 2000px and 1000px variations
  • Creates WebP version for supporting browsers
  • Had to use GD for that, my install of Imagick didn’t have WebP support
  • File upload is sync, but resizing and cloud upload is Queued job so user doesn’t wait
  • Uploads to S3 and cleans up old file versions

The code that renders the image on the frontend cafe page is suuuper rough. Just gets a random size, rather than using srcset. Sniffs webp support and does cloud disk access right in the view. Definitely needs tidying up, still a good start though.

Sourcing 2000px images (wide on retina) might be difficult. A lot of photos on the web seem to be lower quality. Might have to ask users or cafes to submit a quality one!

Is it an App?

I’m trying to hold back on the scope creep and I’d planned not to PWA this thing, but so many people are asking if it’s a real app, or just a website.

Maybe it would be worth doing a 101 PWA with caching of the assets & home screen icon. Unless I do a lot more work none of the features would work offline though.

I’m not sure if that’s pointless, having an online-only PWA, or if in reality most users are online and they just need an icon launcher, fast loading, and no URL bar.

Maybe I’ll do that. If you’re offline I might be able to cache a list of Fav/To Try and at least render those?

Backend Work

I have made progress recently, but mostly on backend processes that I don’t think are super interesting to read about, and certainly not very visual. Stay tuned (if you want).