Drop to-do appen, byg et lille system i stedet

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.

Vælg én sprogstack du kan lære godt i dybden frem for at blande for mange teknologier. Gode valg er f.eks. JavaScript fuldstack (React/Next + Node/Express + Postgres) eller Python (Flask/Django + Postgres); alternativer som Supabase eller Firebase giver backend-funktionalitet hurtigt. Prioritér deploy-venlige værktøjer og et økosystem med god dokumentation, så du kan fokusere på arkitektur og features.
Brug platforme med gratis eller lave barrierer: Vercel til frontend/Next.js, Render/Railway eller Heroku til backend, og Supabase eller Railway til Postgres-databaser. Husk migrations, environment-variabler og et par testbrugere i README, så rekruttereren kan logge ind uden at rode med setup.
Hash altid passwords med et bibliotek som bcrypt, og brug httpOnly cookies og sessions for browserbaserede apps; JWT med refresh tokens er et alternativ for API-first klienter. Hvis du vil spare tid og få best-practices automatisk, kan du bruge en managed auth-løsning som Auth0, Firebase Auth eller Supabase Auth.
Medtag en kort README med installations- og deploy-vejledning, en arkitekturoversigt (komponenter og databasemodeller) og en liste over REST-endpoints. Tilføj også testbrugere eller en screencast, og fremhæv de svære valg du tog - fx autentifikation, dataintegritet og fejl-håndtering.

Ida Balslev er den type ven, der pludselig dukker op i din messenger med et link til en lille web-app, hun lige har bygget for sjov – og bagefter gerne viser dig, hvordan du selv kan lave den. Hendes passion for kodning startede med en hjemmebygget hjemmeside til en hestestald og er langsomt vokset gennem aftener med tutorials, fejlmeldinger og små, hjemmelavede projekter.

På Codingclass.dk deler Ida den viden, hun selv manglede i starten: konkrete eksempler, tydelige forklaringer og ærlige historier om, hvad der typisk går galt første, anden og tredje gang. Hun elsker at tage et abstrakt begreb som fx "API" eller "asynkron JavaScript" og koge det ned til noget, du kan se, klikke på og lege med i browseren. For hende handler kodning ikke om at være perfekt, men om at turde prøve, bryde ting og bygge dem op igen.

Ida skriver især om webudvikling med HTML, CSS og JavaScript, små Python-scripts og grundlæggende koncepter som debugging, versionsstyring og struktur i din kode. Hun tænker altid i næste skridt: når du først forstår idéen, viser hun dig, hvordan du kan udvide det med en ekstra funktion, lidt pænere styling eller en smartere måde at tænke din kode på.

Gennem sine artikler på Codingclass.dk vil Ida gerne give dig følelsen af, at du ikke sidder alene med koden – men at der faktisk er en, der har kæmpet med de samme fejlmeddelelser og nu gerne vil vise dig en vej igennem dem, i et tempo hvor alle kan være med.

Send kommentar

You May Have Missed