Drop to-do appen, byg et lille system i stedet
Et godt porteføljeprojekt er ikke flot, det er gennemtænkt
Hvis din portefølje kun består af to-do lister og klonede tutorials, så lyver den for dig. Ikke fordi de projekter er “forkerte”, men fordi de ikke viser det, en ansættende udvikler faktisk kigger efter.
Et godt porteføljeprojekt i webudvikling viser, at du kan få ting til at hænge sammen. Ikke bare UI. Hele vejen fra database til bruger, inklusiv alle de små irriterende hjørner som login, fejl, data der ikke forsvinder når man refresher, og ting der virker både lokalt og online.
Jeg har kigget på mange “junior porteføljer” efterhånden. Mønstret går igen: enten er projekterne mikroskopiske (to-do, lommeregner, statisk side), eller også er de kæmpe urealistiske monstre, der aldrig bliver færdige (fuld webshop med betaling, anbefalinger, admin panel og rapporter).
Hvis du vil bygge et portefølje projekt i webudvikling, der reelt siger “jeg kan tænke og bygge som en udvikler”, så har du brug for ét lille system, ikke ti halve idéer. En app, der både har:
• auth (login/logud, måske roller)
• API med flere endpoints
• en database, som faktisk bliver brugt rigtigt
Og stadig er lille nok til, at du kan gøre det færdigt, vise det frem og forklare det. Det er det, vi bygger her.
Projektet du bygger: en lille booking- eller registreringsapp
Jeg kalder det en “mini booking/registrerings-app”, men du vælger selv temaet. Det vigtige er strukturen, ikke om det handler om frisørtider eller D&D-sessions.
Konceptet er simpelt: en bruger kan logge ind, se nogle “slots” eller “events” og enten oprette noget eller reservere noget. Du har en admin-rolle, der kan styre data, og en almindelig bruger-rolle, der kan interagere med det.
Nogle mulige varianter:
• En simpel bookingapp til øve-lokaler i et musikfællesskab
• En registreringsapp til små workshops (begrænset antal pladser)
• En tidspost-liste, hvor man kan reservere gaming-sessioner på en fælles konsol
• En “holdstyringsapp” hvor man tilmelder sig træningshold
Pointen: det er det samme mønster. Du har brugere, du har en eller anden form for “ressource” (lokale, event, hold, tid), og du har en relation mellem de to (booking/tilmelding).
Det her er lige præcis sådan et projekt, hvor du kan demonstrere full stack tænkning uden at drukne dig selv i features. Du får:
• login
• database-relations-arbejde
• REST API
• et lille, men rigtigt UI
Og du kan deploye det, så en rekrutteringsperson kan klikke rundt i det. Det slår en pæn Figma-prototype alle dage.
MVP’en: de få features der rent faktisk beviser noget
Hvad du bygger (det, der giver værdi)
Når jeg siger “MVP”, mener jeg ikke “den grimmeste ting du kan slippe afsted med”. Jeg mener “mindste version, der viser de tekniske pointer”.
Her er kernen af din app:
1. Brugersystem med registrering og login
• Opret bruger med email + password
• Login med samme
• Logud-knap der faktisk rydder session/token
2. To roller
• admin: kan oprette, redigere og slette events/slots
• normal bruger: kan se events og tilmelde/afmelde sig
3. Opret og list events
• Admin kan oprette et event med titel, beskrivelse, dato/tid og max antal deltagere
• Forside der viser en liste af kommende events
4. Detaljevisning
• Klik på et event og se detaljer
• Vis hvor mange pladser der er tilbage
5. Tilmelding/afmelding
• Logget ind bruger kan tilmelde sig et event
• Hvis eventet er fyldt, er tilmelding blokeret
• Brugeren kan afmelde sig igen
6. “Mine tilmeldinger” view
• Side hvor bruger kan se de events, de er tilmeldt
7. Simpel validering og fejlbeskeder
• Du kan ikke tilmelde dig samme event to gange
• Du kan ikke redigere events som almindelig bruger
• Fornuftige fejlbeskeder i UI og fra API’et
8. Deployment
• Live URL hvor man kan prøve appen
• GitHub repo med README, korte screenshots og teknisk forklaring
Det er nok til, at en reviewer kan se: database, auth, simple regler, roller, rettigheder, API-design og deployment.
Hvad du bevidst ikke bygger (det, der dræber projektet)
De fleste projekter dør i alt det, man “lige” vil tilføje. Så her er ting, du aktivt vælger fra i første version:
• Ingen betalingsløsning
• Ingen avancerede kalendere med drag-and-drop
• Ingen notifikationer (ingen emails, ingen SMS)
• Ingen “glemt password” flow i v1
• Ingen avatar uploads eller profiler med billeder
• Ingen komplekse filtrerings- og søgefunktioner
• Ingen mobil-app oveni (bliv på web)
• Ingen “organisationer” eller multi-tenant setup
• Ingen ekstra roller udover admin/brugere
• Ingen i18n/oversættelser i første omgang
Hvis du en dag vil udbygge projektet, kan du vælge 1-2 ting fra den liste og bygge videre. Men det første du skal bevise er, at du kan skære fra og gøre noget færdigt.
Data-modellen: så simpelt som muligt, men ikke simplere
De tabeller du faktisk har brug for
Nu bliver det lidt tørt, men det er her, rigtig mange juniorprojekter faktisk falder fra hinanden. Strukturen på data betyder mere end farven på knappen.
Du kan bygge hele appen på 3 tabeller (eller collections, hvis du er mere til NoSQL):
users
• id
• email
• password_hash
• role (“user” eller “admin”)
• created_at
events
• id
• title
• description
• start_time
• max_participants
• created_by (fk til users.id, adminen)
• created_at
registrations
• id
• user_id (fk til users.id)
• event_id (fk til events.id)
• created_at
• unik constraint på (user_id, event_id)
Det er hele kernen. users holder styr på hvem, events holder styr på hvad, og registrations kobler dem sammen.
Hvis du vil lege SQL, kan du ret hurtigt få noget fornuftigt ud af det:
SELECT e.title, e.start_time, COUNT(r.id) AS current_participants
FROM events e
LEFT JOIN registrations r ON r.event_id = e.id
GROUP BY e.id
ORDER BY e.start_time;
Den query er faktisk allerede en lille showcase i sig selv. Den viser, at du forstår relationer og aggregation. Det er den slags, jeg personligt kigger efter, når jeg vurderer et portefølje projekt i webudvikling.
Typiske begynderfejl i datamodel-delen
Jeg har selv lavet dem alle. Flere gange.
1. Gemmer for meget i én tabel
Fx lægger du både events og registreringer i samme tabel og prøver at styre logikken i koden. Det gør alting tungere. Hold relationer i databasen.
2. Ingen unik constraint på (user_id, event_id)
Så kan den samme bruger blive registreret 10 gange til det samme event, og du står og lapper huller i UI’et i stedet. Lad databasen hjælpe dig.
3. Manglende fremmednøgler
Især hvis du bruger SQL: definér foreign keys. Det gør det lettere at forstå, hvad der hænger sammen, når du vender tilbage til projektet om 6 måneder.
Hvis du vil nørde mere med databasestruktur, har vi en artikel om SQL indeks og queries, som faktisk giver mening selv på små projekter.
Auth-delen: simpel login, rigtige mønstre
Valg: session eller JWT?
Hvis du er helt ny, så vælg sessions og cookies. Det er fint. Hvis du er lidt længere, så er en kort JWT-baseret løsning også ok. Det vigtige er ikke moden, men at du viser, at du forstår flowet:
• bruger sender credentials (email, password)
• backend tjekker mod database
• backend udsteder en “bevis” (session-id eller token)
• frontend sender det bevis med på efterfølgende requests
Et meget simpelt login-endpoint kunne se sådan her ud (pseudo/Node-ish):
app.post('/api/login', async (req, res) => {
const { email, password } = req.body;
const user = await db.user.findUnique({ where: { email } });
if (!user) return res.status(401).json({ error: 'Invalid credentials' });
const ok = await comparePassword(password, user.password_hash);
if (!ok) return res.status(401).json({ error: 'Invalid credentials' });
const token = createJwt({ userId: user.id, role: user.role });
res.json({ token });
});
På klienten gemmer du så token i memory eller localStorage (der kan man have en lang snak om sikkerhed, men i et juniorprojekt vil jeg langt hellere se, at du har et klart flow end at du har kopieret et “perfekt” setup uden at forstå det).
Roller og beskyttede routes
Den anden vigtige del er, at du faktisk bruger rollen til noget. Admin skal kunne oprette/rette/slette events. Almindelige brugere må ikke.
Backend-mæssigt kan du skrive en lille middleware:
function requireRole(role) {
return (req, res, next) => {
if (!req.user || req.user.role !== role) {
return res.status(403).json({ error: 'Forbidden' });
}
next();
};
}
app.post('/api/events', requireRole('admin'), async (req, res) => {
// create event
});
Det er simpelt, men det viser, at du har forstået, at auth ikke kun er “kan logge ind”, men også rettigheder på handlinger.
De klassiske auth-fejl i små projekter
1. Ingen server-side beskyttelse
Hvis din frontend bare skjuler knapper, men API’et stadig lader alle slette events, så har du ikke auth, du har pynt.
2. Man kan ikke logge ordentligt ud
Session/token bliver ikke slettet, og brugeren kan refreshe og stadig være logget ind. Det ser sjusket ud og får projektet til at føles ufærdigt.
3. Ingen password hashing
Gem aldrig password i klartekst. Brug bcrypt eller lignende. Også i et “lille” projekt.
API-designet: tænk som en der skal bruge det, ikke som en der skriver det
De få endpoints, du har brug for
Du skal ikke opfinde en hel standard, men du må gerne vise, at du kan følge nogle simple principper. Her er et realistisk sæt endpoints:
Auth
• POST /api/register
• POST /api/login
• GET /api/me
Events
• GET /api/events (liste)
• GET /api/events/:id (detaljer)
• POST /api/events (admin)
• PUT /api/events/:id (admin)
• DELETE /api/events/:id (admin)
Registrations
• POST /api/events/:id/register
• DELETE /api/events/:id/register
• GET /api/my-registrations
Allerede her har du nok til at tale om REST, ressourcer, og at du forstår forskellen på GET/POST/PUT/DELETE.
Fejlformat der ikke driver alle til vanvid
Hvis du vil score ekstra point, så gør dit fejlformat ens. En begynder-fælde er, at hver endpoint returnerer fejl på sin helt egen måde.
Lav i stedet noget i den her stil:
{
"error": {
"code": "EVENT_FULL",
"message": "This event is already full.",
"details": {
"eventId": 123
}
}
}
På den måde kan din frontend vise en pæn fejlbesked, og en reviewer kan se, at du har tænkt over kontrakten mellem frontend og backend. Hvis du har læst vores artikel om API-design, kan du genbruge meget af strukturen derfra.
Lille smule filter/pagination uden at overgøre det
Du behøver ikke lave avanceret pagination, men et simpelt query param eksempel viser, at du kan tænke videre end “giv mig bare alt”.
Fx:
• GET /api/events?upcoming=true
• GET /api/events?page=1&pageSize=10
Og så implementerer du en meget simpel offset pagination på serveren. Det er nok til at vise, at du kender mønstret.
Deployment: få det ud af din maskine
Vælg en stack du faktisk kan få deployet
Det er her, mange projekter snubler. Koden er “færdig”, men ingen kan prøve det. “Virker på min maskine” giver nul point i en portefølje.
Hvis du laver et klassisk setup med:
• React/Vue/Svelte frontend
• Node/Express backend
• Postgres database
så kan du f.eks. gøre:
• Frontend på Vercel eller Netlify
• Backend på Render, Railway eller Fly.io
• Database via supabase, Railway, Render eller Neon
Du kan også tage den simple vej og bygge det hele i en platform som en host der gør deploy nemmere, men sørg for, at du stadig kan forklare, hvad der sker.
Miljøvariabler og små migrations
Du får bonus, hvis du viser, at du kender til environment variables:
• DATABASE_URL
• JWT_SECRET
• NODE_ENV
Og du kort forklarer i README, hvordan man sætter dem lokalt vs i produktion.
Hvis du bruger et ORM som Prisma, kan du også nævne migrations. Det behøver ikke være avanceret, men en lille note om “kør npx prisma migrate dev” giver et seriøst indtryk. Du viser, at du har forstået, at databasen har en struktur, der styres af kode.
Sådan dokumenterer du projektet, så det faktisk imponerer
README som en lille historie om projektet
En god README er næsten lige så vigtig som koden, hvis du spørger mig. Det er her, du oversætter tekniske valg til noget, en anden udvikler kan forstå hurtigt.
En simpel skabelon kunne være:
1. Kort beskrivelse
Et par linjer om hvad appen gør, og hvem den er for.
“En lille booking-app til at administrere workshops. Admin kan oprette events med antal pladser, brugere kan tilmelde og afmelde sig.”
2. Tech stack
• Frontend: fx React + Vite
• Backend: Node + Express
• Database: Postgres + Prisma
• Hosting: Vercel + Render
3. Hvad projektet demonstrerer
Her rammer du bevidst, hvad en reviewer kigger efter:
• Authentication med roller
• Simpel data-model med relationer (users, events, registrations)
• REST API med konsistente svarformater
• Deployment til produktion med environment variables
4. Sådan kører du det lokalt
• git clone
• installer pakker
• sæt .env-filer
• kør migrations
• start backend og frontend
Fx:
git clone ...
cd app
cp .env.example .env
npm install
npm run dev
5. Arkitektur-oversigt
Et lille afsnit om, hvordan ting hænger sammen. Én figur, hvis du har lyst. Ikke noget fancy, bare en kort forklaring.
6. Kendte begrænsninger og fravalg
Og her skiller du dig virkelig ud. Du skriver f.eks.:
• Ingen “glemt password” flow endnu
• Ingen e-mail notifikationer
• Ingen avanceret filtrering af events
• Ingen rate limiting på API endnu
Det viser selvindsigt. Du lader ikke bare som om, projektet er perfekt. Du forklarer, hvad du ville bygge som næste skridt, hvis du havde mere tid.
Screenshots og små use cases
Et par velvalgte screenshots siger mere end 20 linjer tekst. Fx:
• forside med event-liste
• admin-view hvor du opretter et nyt event
• brugers “mine tilmeldinger” side
Skriv 1-2 linjer under hvert screenshot om, hvad man ser. Ikke for grafikken, men for flowet.
Du kan også beskrive en konkret use case i README:
“Som admin opretter jeg ‘Intro til webudvikling’ med 10 pladser. Som bruger logger jeg ind, tilmelder mig eventet, og ser det på min ‘Mine tilmeldinger’ side. Når eventet er fuldt, blokerer API’et nye tilmeldinger og returnerer en fejl med koden EVENT_FULL.”
Så ved læseren præcis, hvordan de kan teste appen, og hvad du har tænkt over.
Hvordan en reviewer faktisk vurderer dit projekt
Hvad din kode siger om dig
Når jeg kigger på et portefølje projekt i webudvikling, har jeg en slags mental tjekliste. Ikke et officielt skema, bare noget i den her retning:
1. Kan jeg køre det?
Hvis README’en er mangelfuld, og jeg skal gætte mig til scripts og env vars, falder det allerede i niveau.
2. Er der en rød tråd i strukturen?
Mapper, filnavne, API-routes, komponenter. Føles det organiseret, eller ligner det en skraldespand?
3. Forstår appen simple regler?
Ingen dobbelt-tilmeldinger. Ingen tilmelding til event i fortiden. Ingen adgang til admin-actions for almindelige brugere.
4. Kan personen forklare fravalg?
README, commit-beskeder eller små kommentarer der viser, at du aktivt har skåret ting fra for at kunne blive færdig.
5. Ser jeg helhedstænkning?
Database, backend, frontend, deploy. Det må godt være småt, men jeg vil gerne se, at du har været hele vejen rundt.
Det er den slags ting, der gør, at et ellers lille projekt pludselig er langt mere interessant end “endnu en klon af en YouTube-tutorial”.
Et konkret næste skridt til dig
Hvis du er typen, der samler på tutorial-ting, så vil jeg faktisk anbefale, at du sletter “to-do app” fra planerne for din næste porteføljeopdatering. Byg i stedet den her lille booking/registreringsapp, og gør den helt færdig: auth, API, database, deploy, README.
Når det spiller, kan du altid skrue op: tilføje bedre performance (hint: tjek artiklen om web performance), mere avancerede queries eller små kvalitetsforbedringer på din frontend.
Men først skal du bevise for dig selv, at du kan bygge et lille, sammenhængende system. Det er meget mere værd end endnu en flot, men tom, demo.









Send kommentar
Du skal være logget ind for at skrive en kommentar.