(Read the English translation đŹđ§)
Warning
Detta repo innehÄller kÀllkoden till projekt-delen av mitt gymnasiearbete, i och med att det nu Àr godkÀnt sÄ kÀnner jag att den har uppfyllt sitt syfte, dÀrmed har jag arkiverat den
-
-
Eftersom att jag bygger en webbshop sÄ kommer jag behöva bra SEO. Bra SEO Àr inte nÄgot som en standard SPA erbjuder, sÄ dÀrför blir man tvungen till att antingen server rendera den eller skriva typ rÄ HTML. Server rendering lÄter ju trevligare.
Jag valde att anvÀnda mig av nextjs dÄ det typ Àr det enda sÀttet att server rendera React och samtidigt anvÀnda de nya server komponent mönsterna.
-
Server komponenter Àr det sjÀlvklara sÀttet att göra server rendering och data fetching pÄ. Jag anvÀnder de sÄ mycket jag kan.
-
-
Jag tycker om konceptet med unidirectional data flow och global state som Redux populariserade. Men jag ogillar all setup, boilerplate, och komplexitet som kommer med Redux.
Jag valde Zustand för att konceptet Àr identiskt till Redux, men implantationen Àr betydligt enklare.
-
Jag valde att anvÀnda Tanstack Query i admin panelen för att hantera dels data fetching, och dels caching av data.
-
Jag rÄkade hitta Nuqs i en github trÄd nÀr jag letade information om hur man hanterar URL query params i nextjs appar, och Nuqs visade sig vara den perfekta lösningen. APIn Àr exakt som en useState, men staten synkroniseras automagiskt med URL quires. Repot förtjÀnar mer stjÀrnor
-
-
-
I min erfarenhet sÄ Àr Tailwind det absolut enklaste sÀttet Àr göra styling pÄ.
-
Heroicons brukar vara min go-to för ikoner. De har inte den största urvalet, men alla av ikonerna ser bra ut, och dessutom har de outlineade versioner.
-
Om man redan anvÀnder React och Tailwind sÄ Àr Shadcn ett sjÀlvklart val.
Det som skiljer Shadcn/ui Ät all de andra komponent biblioteken Àr att du sjÀlv Àger Àger komponenterna. Om du vill Àndra nÄgonting pÄ de sÄ kan du helt enkelt bara öppna komponenten och Àndra det sjÀlv.
-
-
-
-
Jag valde Hono för att den har ett API som efterliknar express, men Àr kompatibel med Bun runtime och har allmÀnt bÀttre prestanda.
-
Jag valde drizzle som min ORM för att APIn efterliknar vanlig SQL.
-
JWT signering och verifiering för att hantera authentication.
-
För att kryptera lösenorden.
-
-
Jag valde mySql som min databas dels för att lÀra mig nÄgot nytt och dels för att en e-handels hemsida Àr full av relationer, sÄ att SQL passar perfekt
-
-
Majoriteten av gĂ„ngerna sĂ„ anvĂ€nder jag inte ens Typescript korrekt đ, men Ă€ndĂ„ sĂ„ Ă€r det en enorm hjĂ€lp för att förebygga buggar, speciellt pĂ„ backenden, dĂ€r man inte alltid Ă€r sĂ€ker pĂ„ vad alla funktioner returnerar
-
Jag vill inte spendera tid och mental energi pĂ„ att formatera min kod, sĂ„ jag valde att anvĂ€nda prettier (dock Ă€r sidoeffekten att man köttar CMD+S efter typ varje knapp tryck đ, men det kan jag leva med). Jag anvĂ€nder Import-sort pluginet ifrĂ„n trivago och Tailwind-classname-sort-pluginet, de Ă€r nicee
Eslint anvÀnder jag helt enkelt med default instÀllningarna som Nextjs kommer med.
-
Inputs behöver valideras, annars sÄ kommer anvÀndare kunna skicka all möjligt skit till backenden, det vill vi inte tillÄta.
Det populÀraste validerings biblioteket Àr nog Zod. Nackdelen med Zod Àr att import storleken Àr (onödigt) stor. Valibot kan ofta ha en import storlek som Àr 10x mindre Àn Zod. Och sÄ föredrar jag Valibots dokumentation.
-
Jag anvÀnde Postman mest bara för att kolla formen av min JSON, det Àr as nice att ha det pÄ andra skÀrmen.
-
Jag anvÀnde Dbeaver för att enklare kunna hantera och visualisera min databas
-
Jag anvÀnder docker för att förenkla hostingen av min Bun backend
-
Bilden Àr en visualisering av databasen skapad med Dbeaver.
Detta var mina krav pÄ databasen:
- Kunna sÀlja produkter
- Kunna ha olika mÀrken och olika kategorier
- Kunna sÀlja variationer av produkter, typ storlek och fÀrg
- Kunna ha rabatter pÄ vissa variationer av produkter, men inte andra
- Kunna belysa en viss variation av en produkt
- Kunna ha ha unika bilder pÄ varje variation
- Admins ska kunna se vad alla har i kundvagnarna, tom de som inte Àr registrerad
Jag valde att bygga ut hela "produkt" grejen med att tÀnka pÄ varje variation av en produkt som en artikel, och sedan ha anonnser som innehÄller flera artiklar. Annonserna behöver dÀrmed ocksÄ ha nÄgon typ av "default" artikel
-
Jag anvĂ€nder đŒ Vercel
-
Jag kör min backend kod i en đł Docker container med đ Railway
-
HĂ€r anvĂ€nder jag đ Railway igen
-
Databas: snake_case
-
API Route namn: kebab-case
-
JS/TS Code: camelCase
-
Client-Side Storage: camelCase
-
Types och Schema validering: PascalCase
-
Environment variabel: SCREAMING_SNAKE_CASE
-
Extra: Databas tabeller ska ha Tbl som suffix
Jag valde dessa conventioner för att simplifiera och streamlina utvecklings processen och samtidigt följa best-practices. Tanken bakom de Àr ju att jag som utvecklare inte ska behöva fundera pÄ triviala grejer som namn givning, samt att man inte ska behöver tÀnka typ "fan, vad heter den endpointen igen?".
Detta projekt vart fullt av lĂ€rdomar för mig. Jag stötte pĂ„ alla sorters problem, allt ifrĂ„n att jag lĂ„ste ut mig sjĂ€lv ifrĂ„n min egen databas, till att jag satt i timmar med en ".Dockerfile", som borde hetat "Dockerfile" đ.
-
LĂ€s
Detta Ă€r faktiskt andra gĂ„ngen jag har försökt att bygga detta för fösta gĂ„ngen sĂ„ blev det kaos pga min state management lösning inte var genomtĂ€nkt. Hela Kundvagnen var lagrad i sin egen komponent som lĂ„g relativt lĂ„ngt in i DOM trĂ€det, sĂ„ det blev vĂ€ldigt svĂ„rt för andra komponenter (som köp-knappen) att komma Ă„t den. Jag insĂ„g det rĂ€tt snabbt att jag borde ha anvĂ€nt mig av (i alla fall) en context run hela skiten. Men hela dev-ex:en (och dĂ€rmed min motivation đ) hann gĂ„ till bajs innan jag faktiskt bytte den till en context.
NÀr jag byggde-om den sÄ viste jag ifrÄn första början att jag var tvungen att lösa state management pÄ nÄgot genomtÀnkt men samtidigt simpelt sÀtt. SÄ jag valde att testa Zustand, och det funkar fint tycker jag.
-
LĂ€s
Detta Àr första projektet som jag anvÀnde SQL i. NÀr jag började bygga ut backenden sÄ tÀnkte jag att det skulle gÄ bra med att skriva rÄ SQL. SÄ jag valde att skapa stored procedures, som jag sedan skulle anropa i koden. Jag insÄg snabbt att det var ett vÀldigt dÄligt mönster, för jag var ju tvungen till att anvÀnda parametrized queries (för att skydda mot SQL-injections) och dÄ blev det ju typ 7 rader kod för en enkel CRUD operation (som dessutom inte ens var type-safe), och koden blev vÀldigt svÄrlÀst.
DÄ fick jag den genialiska idén att abstrahera bort de 7 raderna till sin egen funktion. Sen insÄg jag hur efterblivet det egentligen var; jag hade skapat en helper funktion för varje stored procedure för att förenkla lÀsbarheten av koden, men i processen sÄ gjorde jag det mycket vÀrre. Relativt enkla CRUD-operationer hade sina egna helper funktioner som i sin tur kallade pÄ stored procedures, som i sin tur faktiskt utförde CRUD-operationerna i databasen. Man kan ju inte hÄlla pÄ sÄ om man ska bygga nÄgot underhÄllbart.
SĂ„ jag valde att utforska lite om vilka alternativ som fanns. Jag hamnade mellan Prisma och Drizzle ORM. BĂ„da verkade vara kompetenta lösningar. Jag rĂ„kade dock radera hela min databas nĂ€r jag försökte insallera Prisma (jag missuppfattade vad "database migration" egentligen syftar pĂ„ đ), sĂ„ frustrationen ledde mig till đïž Drizzle đ.
Jag tycker faktiskt att Drizzle passade bÀttre Àn Prisma. pga att APIn efterliknar vanligt SQL-kod (som jag fösöker bli mer bekant med).
-
LĂ€s
State i backend Àr ett helt nytt koncept för mig, före detta projektet sÄ tÀnkte jag aldrig ens pÄ det. API ruttarna i Next Àr stateless, i mitt fall sÄ Àr det ett problem eftersom att det betyder att vartenda rutt kommer att göra sin egen anslutning till databasen. DÄ hade jag min databas pÄ RDS som hade en max-anslutning pÄ 60, och nÀr man har Next i dev-mode sÄ kommer anslutningarna inte att disconnecta pÄ hot-realods, sÄ att de 60 anslutningarna fylldes jÀvligt snabbt.
Varje individuella rutt har ju sin egen state, sÄ först tÀnkte jag att jag kanske skulle kunna utnytja det genom att ha nÄgon typ av intern rutt som returnerar databas anslutnings objektet. Men det visade sig komplexa objekt (som databas anslutningar) inte kunnde skickas genom HTTP :(.
SjÀlv tycker jag att Next borde ha nÄgon inbyggd lösning pÄ detta, men samtidigt sÄ kommer de ju aldrig göra det med tankte pÄ att de tror att man borde göra typ allt i server-komponenter.
Lösningen Ă€r ju att man har nĂ„gon typ av "pooling". Prisma har nĂ„tt magiskt rust-lager som hjĂ€lper till med det, men jag valde ju Drizzle đ. Som tur Ă€r sĂ„ kan man ju ocksĂ„ ha pooling pĂ„ databas-nivĂ„, jag försökte fixa det i min AWS RDS panel, men det ville inte fungera, sĂ„ jag bestĂ€mde mig för att bygga-om min backend med Bun och Hono.
Motivationen till det var dels ocksÄ att jag började ogilla file-based-routing mer och mer. Jag tycker att file-based-routing fungerar fint pÄ frontenden, men inte pÄ backenden. Motivationen till bygga om den var dels ocksÄ att Next inte har nÄgon riktig middleware lösning för backend rutter, och jag var tvungen att ha typ 10 rader boiler-plate kod i varje "admin/" rutt bara för att checka-av om anropet faktisk kom ifrÄn en admin.
Rent tekniskt sÄ Àr vÀll anslutningen inte riktigt singelton eftersom jag anvÀnder "mysql.createPool". Jag gör det pga att jag stötte pÄ nÄgon typ av timeout-bug dÀr anslutningen stÀngdes efter nÄgon timme, men det var omöjligt att detektera det (förutom om man vill wrappa varje endpoint i en try-catch, vilket man ju inte vill). mysql.createPool hanterar sÄdana grejer Ät mig.
-
LĂ€s
Första gÄngen jag byggde ut admin panelen sÄ tÀnkte jag att jag skulle anvÀnda server-komponenter, men det visade sig vara ett rÀtt dumt val. Server-komponenter renderas ju pÄ servern, nÀr webblÀsaren tar emot de som cachar den de. Det betyder att trots att innehÄllet kan ha Àndrats sÄ kommer webblÀsaren visa den cachade verisonen och inte be servern efter en ny. I praktiken sÄ betyder det att man kan lÀgga till en artikel i admin/articles/add, och sedan nÀr man kommer tillbaks till admin/articles sÄ kommer den nya artikeln inte visas. Denna chachingen gÄr inte att stÀnga av. Dokumentationen sÀger (komiskt nog) typ bara "nej".
Pga av att innehÄllet pÄ admin panelen Àr vÀldigt interaktivt sÄ Àr det nog smartare att bygga ut data fetchingen pÄ clienten istÀllet. Jag har aldrig anvÀnt react query innan, men hÀr passar den faktiskt perfekt.
-
LĂ€s
Bun Àr en relativt ny grej och dÀrmed finns det inga bra no-bullshit guider pÄ att hosta det. Efter lite googling sÄ kom jag fram till att jag var tvungen till att kötta ner den i en docker container. Det finns ju nÄn officiala Dockerfile template pÄ Bun:s hemsida, men jag valde att anvÀnda en ifÄn nÄn artikel pÄ Medium för att den verkade mycket mer simpel.
NÀsta steg blev dÄ att hitta nÄgot system för att hosta dockerfilen. AWS har ju EC2 eller Lambda, men komplexiteten Àr jÀvligt hög, (jag vet inte riktigt hur det hade fungerat, men jag antar) att jag hade först behövt göra nÄgon typ av automatisering som lyssnar pÄ commits pÄ github repot, sen hÀmtar dockerfilen och bygger en docker image ifrÄn den, och sedan hostar den pÄ EC2 eller Lambda. Det lÄter cp-komplicerat, jag ville ha nÄgot mer simpelt.
Med Render kan man bara koppla github repot och sen bara funkar det, och de verkade stödja docker, men cold-startsen Àr brutala (typ 1min). Senare hittade jag att Railway ocksÄ kunde deploya docker (dÀr Àr cold-startsen helt okej).
-
LĂ€s
"Login" knappen Àr nÄgonting som Àr beroÀnde av state. Om anvÀndaren Àr inloggad sÄ ska det stÄ "view account", om den inte Àr inloggad sÄ ska det stÄ "login". Staten gÄr att initialisera pÄ clienten med javascript, men om anvÀndaren inloggad sÄ kommer det stÄ "login" innan sidan hydratisera. Det ser konstigt ut, sÄ jag initialiserade staten med en serverkomponent, sedan tar clienten över.
Lösningen Àr inte 100% optimal eftersom den orsakar en extra rerender, men navigationen Àr en vÀldigt viktig del av UX, sÄ det fÄr man ta.
Railway app har samma problem, men de har inte löst det hah
Detta projekt Ă€r en del av mitt godkĂ€nda Gymnasiearbete pĂ„ HaganĂ€sskolan, Ălmhult (Teknikprogrammet).
Rapporten finns tillgÀnglig som PDF-fil i detta repo, men det Àr enklast att öppna den med nbviewer.
I detta gymnasiearbete presenteras processen för att skapa en e-handelssida, dÀr jag har utforskat och anvÀnt mig av moderna webbteknologier inom bÄde front-end-end och back- end utveckling. Arbetet inkluderar en översikt över relevanta JavaScript-ramverk, databasval mellan SQL och NoSQL, samt en diskussion kring de tekniska beslut som fattats under projektets gÄng. Slutresultatet Àr en fungerande webbshop, med insikter och reflektioner kring de utmaningar och lÀrdomar projektet medfört.
Header för butikssidan | Mest populÀra produkter |
Produktsida | Sök efter varumÀrke |
Sökfilter | Kundvagnen |
Kassan | Admin - Startsida |
Admin - Artiklar | Admin - Redigera artiklar |
Admin - VarumÀrken | Admin - Redigera varumÀrken |
Admin - Kategorier | Admin - Redigera kategorier |
Admin - Listor | Admin - Redigera listor |
Admin - Planerade reor | Admin - Redigera planerade reor |
- Streamline input validation and form submission across the app
- Add success screen after payment, and wipe cart items
- Add to orders table after successful payment??
- Add statistics to admin panel
- Find email provider and setup forgot password system
- Fix behavior if only one color/size is available
- probably shouldn't store user info (apart from userId) on the client
- remove a bunch of unused console logs
- Make so buyers can submit reviews
- Ai integration??? like talk with the cart? let the ai modify the cart??
- fix wierd discount bugg
- Fix checkout for guest users
- Make so that the cancel button on the stripe page brings you back to the prevous page that you were on. pretty much just need to add an argument to the goToCheckout function
- Integrate stripe
- fix error on editing listing, dunno why
- Present account info in a cleaner way, and make it editable
- Add planned sales shit
- Fix bug with red hoodie,
- get nav links from the backend, also put in so that the "listing view page" has a link to view the brand, and a link to view the category
- Build filter/browse section of the website
- Fix weird (race condition?) bug with cart state syncing (probably caused by incorrect implementation of debouncing, would probably be fixed by removing debouncing entirely)
- Build search functionality? (dunno how, but i'll find out)
- FIX BUG where the backend tries to send commands to the database, even though the connection is closed (kinda fixed maybe??)
- Write some tests? idk
- Write a nice readme
- Translate readme
- Write GA loggbok from commit history
- Chill