Hoppa till huvudinnehåll

Bilduppladdning och Filhantering 📸

Ditt uppdrag 🎯

Lägg till bilduppladdning i din app:

  • Låt användare ladda upp profilbilder
  • Visa bilder i din app
  • Förstå olika sätt att lagra bilder

Viktigt: Du ska förstå VARFÖR bilder är annorlunda än vanlig text-data och vilka utmaningar detta medför.

Fas 1: Förstå bildhantering på webben 🧠

Steg 1: Var ska bilderna lagras?

Olika alternativ att fundera på:

Alternativ A: I databasen (Base64)

  • Fördelar: Enkelt, allt på samma ställe
  • Nackdelar: Databas blir stor, långsamt

Alternativ B: På filsystemet

  • Fördelar: Snabbt, mindre databas
  • Nackdelar: Fil-paths kan brytas, backup mer komplext

Alternativ C: Cloud storage (AWS S3, Cloudinary)

  • Fördelar: Skalbart, automatisk backup, CDN
  • Nackdelar: Kostar pengar, mer komplex setup

Frågor att reflektera:

  1. Vad händer med bilderna om databasen kraschar vs filsystemet kraschar?
  2. Hur påverkas prestanda när du laddar en sida med 100 bilder?
  3. Vilken lösning passar bäst för din nuvarande app?

Steg 2: Säkerhetsrisker med filuppladdning

Tänk på dessa hot innan du börjar:

  1. Malicious files: Användare kan ladda upp virus eller skript
  2. File bombs: Extremt stora filer som kraschar servern
  3. Path traversal: Filnamn som försöker komma åt andra filer
  4. Resource exhaustion: För många uploads samtidigt

Skyddsmekanismer att planera:

  • Begränsa filtyper (bara bilder)
  • Begränsa filstorlek
  • Validera filinnehåll
  • Säkra filnamn

Steg 3: HTML form för filer

HTML för filuppladdning är annorlunda:

<!-- Vanlig form -->
<form method="POST">
<input type="text" name="username" />
</form>

<!-- Fil-form behöver speciell encoding -->
<form method="POST" enctype="???">
<input type="???" name="image" />
</form>

Frågor att lösa:

  • Vad ska enctype vara för filuppladdning?
  • Vilken input type används för filer?
  • Vilka attribut kan begränsa vilka filer som accepteras?

Fas 2: Planera din implementation 📋

Steg 1: Välj lagringsstrategi

För denna modul börjar vi med databas-lagring (Base64) eftersom det är enklast att komma igång med.

Vad är Base64?

  • Ett sätt att representera binär data (bilder) som text
  • Kan sparas i vanliga text-fält i databasen
  • Kan visas direkt i HTML med data:image/jpeg;base64,

Experimentera med Base64:

// Hur konverterar man en fil till Base64?
// Hur visar man Base64 som en bild i HTML?
// Vilka är nackdelarna med Base64?

Steg 2: Designa datamodell

Lägg till bild-support i din befintliga app:

// Ska du ha en separat Image-tabell?
model Image {
id String @id @default(uuid())
encoding String // Base64 data
// Vilka andra fält behöver du?
// Filename? FileSize? UploadedAt? UserId?
}

// Eller lägga till bilder direkt på befintliga modeller?
model User {
id String @id @default(uuid())
username String
profileImage String? // Base64 string
// ...
}

Frågor att besluta:

  • Separata Image-tabell eller direkt på User/Forum/etc?
  • Vilka metadata vill du spara om varje bild?
  • Hur kopplar du bilder till ägare?

Steg 3: Planera användarupplevelse

Tänk igenom flödet:

  1. Upload flow: Var kan användare ladda upp bilder?
  2. Preview: Ska användare se preview innan upload?
  3. Progress: Ska du visa progress för stora filer?
  4. Error handling: Vad händer om upload misslyckas?
  5. Management: Kan användare ta bort/byta bilder?

Fas 3: Implementera grundläggande upload 🔧

Steg 1: HTML-formulär

Skapa ett upload-formulär:

<!-- Din uppgift: Fyll i de tomma delarna -->
<form method="POST" action="?/upload" enctype="???">
<input type="???" name="image" accept="???" />
<button type="submit">Upload Image</button>
</form>

Frågor att lösa:

  • Vad ska enctype vara?
  • Vad ska accept vara för att bara tillåta bilder?
  • Hur lägger du till use:enhance för bättre UX?

Steg 2: Server-side hantering

Form action för att ta emot filen:

// Din uppgift: Implementera upload-logiken
export const actions: Actions = {
upload: async ({ request }) => {
const data = await request.formData();
const file = data.get('image') as File;

// Validering - vad ska du kolla?
// - Är det verkligen en fil?
// - Är det en bild?
// - Är den för stor?

// Konvertering till Base64
// Tips: Buffer.from(await file.arrayBuffer()).toString('base64')

// Spara i databas
// Tips: prisma.image.create(...)
}
};

Utmaningar att lösa:

  1. Filvalidering: Hur kontrollerar du att det är en giltig bild?
  2. Storleksbegränsning: Hur begränsar du filstorlek?
  3. Error handling: Vad händer om något går fel?

Steg 3: Visa bilder

Display uploaded images:

<!-- Din uppgift: Visa bilderna -->
{#each data.images as image}
<img src="data:image/jpeg;base64,{/* vad ska stå här? */}" alt="Uploaded image" />
{/each}

Frågor:

  • Hur fungerar data: URLs?
  • Vad händer om bilden är PNG istället för JPEG?
  • Hur kan du hantera olika bildformat?

Fas 4: Förbättra och säkra 🛡️

Steg 1: Filvalidering

Implementera robust validering:

function validateImageFile(file: File) {
// Kontrollera filtyp
const allowedTypes = ['image/jpeg', 'image/png', 'image/webp'];
// Hur kollar du file.type?

// Kontrollera filstorlek (t.ex. max 5MB)
const maxSize = 5 * 1024 * 1024; // 5MB in bytes
// Hur kollar du file.size?

// Kontrollera filnamn för säkerhet
// Inga ".." eller "/" i namnet

// Returnera true/false eller throw error
}

Steg 2: Hantera stora filer

Problem: Stora bilder kan krascha din app eller göra den långsam.

Lösningar att utforska:

  1. Filstorlek-begränsning: Sätt max upload size
  2. Bildkomprimering: Förminska bilder automatiskt
  3. Progressive loading: Visa placeholder medan bild laddar

Render.com konfiguration:

# Lägg till i dina environment variables
BODY_SIZE_LIMIT=Infinity

Varför behövs detta? Fundera på vad som händer utan denna setting.

Steg 3: Förbättra användarupplevelse

Experimentera med dessa features:

Preview före upload:

<script>
let previewUrl = '';

function handleFileSelect(event) {
const file = event.target.files[0];
if (file) {
// Hur skapar du en preview URL?
// Tips: URL.createObjectURL() eller FileReader
}
}
</script>

<input type="file" on:change={handleFileSelect} />
{#if previewUrl}
<img src={previewUrl} alt="Preview" />
{/if}

Upload progress:

<script>
import { enhance } from '$app/forms';

let uploading = false;
</script>

<form
method="POST"
use:enhance={() => {
uploading = true;
return async ({ update }) => {
await update();
uploading = false;
};
}}
>
<input type="file" disabled={uploading} />
<button disabled={uploading}>
{uploading ? 'Uploading...' : 'Upload'}
</button>
</form>

Fas 5: Avancerade tekniker 🚀

Steg 1: Multipla bilder

Utmaning: Ladda upp flera bilder samtidigt

<!-- Multiple file selection -->
<input type="file" multiple accept="image/*" name="images" />

Server-side hantering:

export const actions: Actions = {
uploadMultiple: async ({ request }) => {
const data = await request.formData();
const files = data.getAll('images') as File[];

// Loopa genom alla filer
// Validera varje fil
// Konvertera och spara
}
};

Steg 2: Bildgalleri med CRUD

Skapa ett komplett bildhanteringssystem:

  • Upload: Ladda upp nya bilder
  • Display: Visa alla bilder i ett galleri
  • Delete: Ta bort bilder
  • Update: Byt ut befintliga bilder
  • Organize: Kategorisera eller tagga bilder

Steg 3: Integration med befintliga features

Lägg till bilder i din befintliga app:

Profilbilder för användare:

// Lägg till profileImage i User-modellen
// Upload-formulär på profil-sida
// Visa profilbilder i forum-meddelanden

Bilder i forum-meddelanden:

// Tillåt bilder i forum-posts
// Visa inline bilder i meddelanden
// Hantera permissions (vem får ladda upp var?)

Fas 6: Alternativa lagringsmetoder 💾

Steg 1: Filsystem-lagring

Istället för Base64 i databas:

import { writeFile } from 'fs/promises';
import path from 'path';

export const actions: Actions = {
uploadToFile: async ({ request }) => {
const data = await request.formData();
const file = data.get('image') as File;

// Skapa unikt filnamn
const filename = `${Date.now()}-${file.name}`;
const filepath = path.join('static', 'uploads', filename);

// Spara fil
const buffer = Buffer.from(await file.arrayBuffer());
await writeFile(filepath, buffer);

// Spara bara filnamn i databas
await prisma.image.create({
data: { filename: filename }
});
}
};

Visa filer:

{#each data.images as image}
<img src="/uploads/{image.filename}" alt="Uploaded" />
{/each}

Steg 2: Cloud storage

För production-appar:

Populära alternativ:

  • Cloudinary: Automatisk bildoptimering
  • AWS S3: Billigt, skalbart
  • Google Cloud Storage: Integration med andra Google-tjänster
  • Vercel Blob: Enkelt för Vercel-hosting

Grundkoncept:

  1. Upload fil till cloud service
  2. Få tillbaka en URL
  3. Spara URL i databas
  4. Visa bilder via URL

Utmaningar att lösa själv 🎯

Grundnivå:

  1. Grundläggande upload med Base64 ✓
  2. Filvalidering (typ, storlek) ✓
  3. Visa bildgalleri

Mellannivå:

  1. Multipla bilder samtidigt
  2. Preview före upload
  3. Ta bort bilder funktionalitet

Expertnivå:

  1. Filsystem-lagring istället för Base64
  2. Bildkomprimering automatiskt
  3. Cloud storage integration

Säkerhetschecklista 🔒

✅ Validering:

  • Kontrollera filtyp
  • Begränsa filstorlek
  • Validera filnamn
  • Kontrollera bildinnehåll (inte bara extension)

✅ Säker lagring:

  • Säkra filnamn (inga "../" attacks)
  • Separera uploads från kod
  • Rätt file permissions
  • Backup-strategi

✅ Prestanda:

  • Bildkomprimering för stora filer
  • Lazy loading för gallerier
  • CDN för snabbare leverans
  • Cleanup av oanvända bilder

Vanliga problem och lösningar 🔧

❌ "Request entity too large"

Problem: Filen är för stor för servern Lösning: Sätt BODY_SIZE_LIMIT=Infinity i environment variables

❌ "Multipart form data not supported"

Problem: Glömt enctype="multipart/form-data" Lösning: Lägg till korrekt enctype på formuläret

❌ Bilder visas inte

Problem: Fel Base64 format eller sökväg Lösning: Kontrollera data:image/jpeg;base64, prefix

❌ Minnesläckage

Problem: Stora Base64 strings tar mycket minne Lösning: Överväg filsystem eller cloud storage

Reflektion och lärande 🤔

Efter implementation, reflektera:

Teknisk förståelse:

  • Vad lärde du dig om skillnaden mellan text och binär data?
  • Hur påverkar Base64 vs filsystem prestanda?
  • Vilka säkerhetsrisker upptäckte du?

Användarupplevelse:

  • Känns upload-processen smidig?
  • Hur kan du förbättra feedback till användaren?
  • Vad händer när något går fel?

Skalbarhet:

  • Vad händer om 1000 användare laddar upp bilder?
  • Hur påverkas databas-storlek?
  • När bör du överväga cloud storage?

Steg 3: Lägg till bilder i din auth-app 📸

Använd samma app som du lade till auth i

Nu ska vi lägga till meningsfull bilduppladdning:

Forum-app med auth:

  • Profilbilder för users
  • Bilder i forum-meddelanden
  • Visa författarens profilbild vid meddelanden

Market-app med auth:

  • Produktbilder för items (flera bilder per item)
  • Profilbilder för sellers
  • Visa seller-info med bild i item-listings

Character-app med auth:

  • Character avatars/portraits
  • User profilbilder
  • Visa avatars i character-listor

Implementation-tips:

  1. Börja med profilbilder (enklast)
  2. Lägg till upload på profil-sida
  3. Visa profilbilder i navigationen/headers
  4. Sedan lägg till bilder på huvudentiteter
  5. Visa bilder i listor och detail-vyer

Bonus-utmaning:

Lägg till bildgalleri för items/characters med flera bilder per objekt!

Nästa steg 🚀

Nu när du kan hantera filuppladdning är nästa modul om kryptografi och lösenordssäkerhet - att skydda användardata på rätt sätt!

Du behärskar filhantering! 📁

Filuppladdning är en komplex del av webbutveckling som många utvecklare kämpar med. Att förstå säkerhet, prestanda och användarupplevelse gör dig till en mycket mer komplett utvecklare!

Resurser för fördjupning 📚