Gebruikte tech
Een tijdje geleden was ik er klaar mee: mijn vrouw en ik deden elke week boodschappen en moesten steeds bedenken wat we wilden eten. Daarna was de vraag welke ingrediënten we daarvoor ook al weer nodig hadden. Als we dat allemaal hadden genoteerd was het volgende probleem dat ze niet de volgorde stonden waarin ik wist dat ze in de supermarkt zouden liggen...
Ik ben webdeveloper, dus wat doe ik? Ik maak er een applicatie voor! Tadaa: Shoppier.
Hoe werkt het?
Binnen Shoppier kun je ingrediënten aanmaken. Deze individuele ingrediënten hebben een paar velden: de naam, de eenheden (bijvoorbeeld 500 gram of 1 pot) en iets dat mijn leven een stuk makkelijker maakt: de locatie. Deze ingrediënten kun je vervolgens combineren tot één of meerdere recepten. Bijvoorbeeld, als je de traditionele Nederlandse "Andijviestamppot" wilt maken, heb je de volgende ingrediënten nodig:
Andijvie
Aardappelen
Mosterd
Melk
Boter
Worst
Spekblokjes
Deze ingrediënten maak je dus individueel aan. Daarna gebruik je ze om een recept genaamd "Andijviestamppot" samen te stellen. Zodra je dit recept hebt, kun je een shoppinglist aanmaken en het recept eraan toevoegen. Hierdoor worden automatisch alle ingrediënten aan de lijst toegevoegd en gesorteerd op basis van de ingestelde locatie.
Dit betekent dat als je gedurende de week vijf verschillende recepten gaat maken, alle ingrediënten binnen deze recepten in de juiste volgorde verschijnen, precies zoals ze in de supermarkt(en) liggen. Het is het makkelijkst als je al je boodschappen in één supermarkt doet, maar het werkt ook als je meerdere winkels bezoekt.
Technische details
Ik heb Shoppier ontworpen met een duidelijke separation of concerns. De frontend en backend communiceren via een custom SDK die als een soort contract layer fungeert. Dit zorgt voor type safety in het hele project.
Voor de frontend heb ik gekozen voor React met Typescript en een robuust state management systeem gebouwd met Redux Toolkit en RTK Query. Deze combinatie biedt een goede oplossing voor het beheren van de state en tegelijkertijd worden API-calls afgehandeld met automatische caching, loading states en error handling.
De UI is gebouwd met Mantine. Ik heb gefocused op responsive design en me vooral gericht op mobiel, omdat dat is waarop deze applicatie voornamelijk gebruikt zal worden.
De backend draait op ExpressJS en Typescript en biedt een RESTful API voor de frontend. Ik heb TypeORM gekozen als de ORM-laag, waardoor ik duidelijke entity relationships kon definiëren en complexe queries eenvoudig kon uitvoeren. Het databaseschema omvat entiteiten voor gebruikers, items, recepten, boodschappenlijsten en de relaties daartussen.
De error handling in de backend is uitgebreid, met custom error classes die betekenisvolle foutmeldingen naar de frontend sturen. Dit maakt het debuggen eenvoudiger en verbetert de gebruikerservaring door correcte foutmeldingen te tonen.
Misschien is het meest unieke aspect van dit project de custom SDK die ik ontwikkeld heb. Dit gedeelde pakket bevat alle interface-definities, request/response types en algemene utilities die zowel door de frontend als de backend worden gebruikt.
Door deze definities te centraliseren, zorg ik ervoor dat elke wijziging in het datamodel automatisch wordt weerspiegeld in zowel de client- als de servercode. Dit verkleint de kans op inconsistenties aanzienlijk en maakt de codebase beter onderhoudbaar.
De SDK wordt gepubliceerd als een npm-package waar zowel de frontend als de backend van afhankelijk zijn. Dit betekent dat wanneer ik een model of API-contract update, ik simpelweg de SDK-versie in beide projecten aanpas. Deze werkwijze zorgt ervoor dat de frontend en backend altijd synchroon blijven.