Used tech
A while ago I got sick and tired of me and my wife doing groceries for the week, and wondering what to eat. And then when we knew what we wanted to eat, what ingredients did we need for that dish again? And then, when I noted down all the ingredients of everything, it wasn't in the proper order that I knew it would appear in the supermarket.
I'm a webdeveloper, so what do I do? Make an application for it! Queue: Shoppier.
How does it work?
Within shoppier you can create ingredients. These individual ingredients have a few options: the name, the measurement, 500 grams or 1 jar for example, and the thing what makes my life a lot easier: location.
These individual ingredients can be put together into one or many recipes. For example, if you'd like traditional Dutch "Andijviestamppot" you need a few ingredients:
Endive
Potatoes
Mustard
Milk
Butter
Sausage
Bacon cubes
So, these ingredients are what you'll have to create individually. Then, you can use them to create a recipe called "Andijviestamppot". Now that you have this recipe, you can create a shoppinglist and add this recipe to it. It will automatically add all the ingredients to this shoppinglist, and sort them according to the location you've set.
This means if you're going to eat 5 different recipes throughout the week, all the ingredients within these recipes will be properly ordered in the order of appearance in the supermarket(s) you visit. It's easiest if you do groceries in a single supermarket, but it still work if you do them in multiple as well.
Technical details
I architected Shoppier with a clear separation of concerns. The frontend and backend communicate through a custom SDK that serves as sort of a contract layer. This ensure type safety throughout the entire project.
For the frontend I chose React with Typescript and built a robust state management system using Redux Toolkit and RTK Query. This combination provides a good solution for handling application state, while also handling API calls with automatic caching, loading states and error handling.
The UI is built with Mantine. I implement responsive design and focused mostly on mobile, because that's where this application should be used.
The backend is powered by ExpressJS and Typescript, providing a RESTful API for the frontend. I chose TypeORM as the ORM layer, which allowed me to define clear entity relationships and implement complex queries with ease. The database schema includes entities for users, items, recipes, shoppinglists and the relationships between all of these.
Error handling in the backend is comprehensive, with custom error classes that provide meaningful error messages on the frontend. This makes debugging easier and improves the user experience by showing proper error messages.
Perhaps the most unique aspect of this project is the custom SDK I developed. This shared package contains all the interface definitions, request/response types, and common utilities used by both the frontend and backend.
By centralizing these definitions, I made sure that any change to the data model is automatically reflected in both the client and server code. This drastically reduces the potential for inconsistencies and makes the codebase more maintainable.
The SDK is published as an npm package that both the frontend and backend depend on, which means that when I update a model or API contract, I simply update the SDK version in both projects. This workflow ensures that the frontend and backend remain in sync at all times.