NestJS-API-er for virksomhetsbackend · leksjon

Direkteopplastinger til S3 med forhåndssignerte URL-er

Avlast tunge opplastinger til objektlagring ved å utstede kortlivede, forhåndssignerte URL-er fra API-et.

Leksjon 3 av 413 trinn

Direkteopplastinger til S3 med forhåndssignerte URL-er er en gratis leksjon i NestJS-API-er for virksomhetsbackend på CoddyKit. Dette er leksjon 3 av 4. Du kan lese hele leksjonen gratis nedenfor – og deretter øve praktisk i nettleseren med en innebygd kodeeditor og en AI-veileder som er tilgjengelig døgnet rundt. Den er en del av læringsløpet i NestJS-API-er for virksomhetsbackend, og fremdriften din synkroniseres mellom nettet og CoddyKit-appen. Kurset i NestJS-API-er for virksomhetsbackend inneholder totalt 4 leksjoner.

Hvorfor ikke videresende opplastinger gjennom API-et?

Når en klient laster opp en stor fil, sender den naive utformingen bytene til NestJS-API-et Deres, som deretter videresender dem til objektlagring. Dette gjør serveren til en flaskehals.

  • Belastning på minne og CPU — hver opplasting opptar en forespørselstråd og bufres/strømmes gjennom prosessen Deres.
  • Dobbelt båndbreddeforbruk — byte går klient → API → S3, så De betaler for de samme dataene to ganger.
  • Tidsavbrudd for forespørsler — lastbalanserere (f.eks. ALB og Nginx) begrenser hvor lenge en forespørsel kan vare; opplastinger på flere GB stopper opp.

Løsningen er å la nettleseren laste opp direkte til S3. API-et Deres utsteder bare en kortvarig, signert URL som gir tillatelse til én bestemt operasjon.

Hva er en forhåndssignert URL?

En forhåndssignert URL er en vanlig S3-objekt-URL med ekstra spørringsparametere som koder en midlertidig, kryptografisk signert tillatelse. Alle som har URL-en, kan utføre nøyaktig én operasjon (f.eks. PutObject) på nøyaktig én nøkkel frem til den utløper.

  • Signert med AWS-legitimasjonen Deres, men legitimasjonen blir aldri eksponert — bare signaturen blir det.
  • Begrenset til én HTTP-metode, én bucket og én objektnøkkel.
  • Har en fast utløpstid (sekunder), og etter dette avviser S3 den med 403.

Fordi S3 validerer signaturen selv, håndterer API-et Deres ikke filbytene i det hele tatt.

Opplastingsflyten

Flyten fra start til slutt har tre aktører: nettleseren, NestJS-API-et Deres og S3.

  • 1. Forespørsel: Nettleseren spør API-et: «Jeg vil laste opp avatar.png, 240 KB, image/png.»
  • 2. Signering: API-et validerer forespørselen, genererer en unik nøkkel og returnerer en forhåndssignert PUT-URL.
  • 3. Opplasting: Nettleseren gjør PUT av råbytene direkte til denne URL-en på S3.
  • 4. Bekreftelse: Nettleseren informerer API-et om at opplastingen lyktes; API-et lagrer nøkkelen i databasen.

API-et Deres forblir raskt og tilstandsløst — det videresender aldri nyttelasten.

Konfigurering av S3-klienten

Bruk de modulære pakkene i AWS SDK v3. Opprett én enkelt S3Client-instans og del den via en NestJS-provider, slik at legitimasjon og region konfigureres på ett sted.

Legitimasjonen kommer fra miljøvariabler (eller, i produksjon, en IAM-rolle). Legg dem aldri direkte inn i kildekoden.

import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { S3Client } from '@aws-sdk/client-s3';

@Injectable()
export class S3Provider {
  readonly client: S3Client;
  readonly bucket: string;

  constructor(private readonly config: ConfigService) {
    this.bucket = config.getOrThrow<string>('S3_BUCKET');
    this.client = new S3Client({
      region: config.getOrThrow<string>('AWS_REGION'),
      credentials: {
        accessKeyId: config.getOrThrow<string>('AWS_ACCESS_KEY_ID'),
        secretAccessKey: config.getOrThrow<string>('AWS_SECRET_ACCESS_KEY'),
      },
    });
  }
}

Generering av en forhåndssignert PUT-URL

Pakken @aws-sdk/s3-request-presigner signerer en kommando uten å utføre den. De bygger en PutObjectCommand som beskriver målnøkkelen og innholdstypen, og kaller deretter getSignedUrl med en expiresIn.

  • ContentType i kommandoen håndheves: nettleseren må sende en samsvarende Content-Type-header.
  • expiresIn angis i sekunder — hold den kort (60–300 s), slik at lekkede URL-er raskt blir ugyldige.
import { Injectable } from '@nestjs/common';
import { PutObjectCommand } from '@aws-sdk/client-s3';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
import { S3Provider } from './s3.provider';

@Injectable()
export class UploadsService {
  constructor(private readonly s3: S3Provider) {}

  async createUploadUrl(key: string, contentType: string): Promise<string> {
    const command = new PutObjectCommand({
      Bucket: this.s3.bucket,
      Key: key,
      ContentType: contentType,
    });
    return getSignedUrl(this.s3.client, command, { expiresIn: 120 });
  }
}

Generering av trygge, unike objektnøkler

Stol aldri på klientens filnavn som S3-nøkkel. En bruker kan sende ../../etc/passwd eller kollidere med en annen brukers fil. Generer nøkkelen på serversiden.

  • Opprett navnerom etter eier: uploads/{userId}/..., slik at autorisasjon blir enkel å forstå.
  • Bruk en tilfeldig UUID for å garantere unikhet.
  • Behold bare en renset filendelse for hint om innholdstype og verktøystøtte.
import { randomUUID } from 'node:crypto';
import { extname } from 'node:path';

function buildObjectKey(userId: string, originalName: string): string {
  const ext = extname(originalName).toLowerCase().replace(/[^.a-z0-9]/g, '');
  const safeExt = /^\.[a-z0-9]{1,8}$/.test(ext) ? ext : '';
  return `uploads/${userId}/${randomUUID()}${safeExt}`;
}

console.log(buildObjectKey('user-42', 'My Vacation Photo.PNG'));
console.log(buildObjectKey('user-42', 'sneaky/../../etc/passwd'));

Kontrollerendepunktet

Eksponer et beskyttet endepunkt som mottar filmetadataene, validerer dem med en DTO og returnerer den forhåndssignerte URL-en sammen med den endelige nøkkelen. Klienten trenger nøkkelen senere for å bekrefte opplastingen eller bygge URL-en for offentlig tilgang/lesing.

Autentisering er viktig her: signeringsendepunktet er tilgangskontrollporten Deres. S3 stoler på alle gyldige signaturer, så alle kontroller (hvem, hvilken størrelse og hvilken type) må utføres før De signerer.

import { Body, Controller, Post, UseGuards, Req } from '@nestjs/common';
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
import { UploadsService } from './uploads.service';
import { CreateUploadDto } from './dto/create-upload.dto';

@UseGuards(JwtAuthGuard)
@Controller('uploads')
export class UploadsController {
  constructor(private readonly uploads: UploadsService) {}

  @Post('presign')
  async presign(@Req() req, @Body() dto: CreateUploadDto) {
    return this.uploads.presignForUser(req.user.id, dto);
  }
}

Validering av DTO-en for opplastingsforespørselen

Valider metadataene før signering. Avvis MIME-typer som ikke er tillatt, og for store filer på API-laget — men husk at klienten kan lyve, så dette er en første forsvarslinje, ikke den siste.

  • Bruk en tillatelsesliste for contentType.
  • Begrens den oppgitte size-verdien for å avvise åpenbart enorme opplastinger raskt.
import { IsIn, IsInt, IsString, Max, Min } from 'class-validator';

const ALLOWED = ['image/png', 'image/jpeg', 'image/webp', 'application/pdf'] as const;

export class CreateUploadDto {
  @IsString()
  filename: string;

  @IsIn(ALLOWED)
  contentType: (typeof ALLOWED)[number];

  @IsInt()
  @Min(1)
  @Max(10 * 1024 * 1024) // 10 MB
  size: number;
}

Håndheving av størrelse med en signert Content-Length

Størrelseskontrollen i DTO-en er bare veiledende — nettleseren bestemmer fortsatt hvor mange byte den faktisk gjør PUT av. For å få S3 til selv å avvise for store opplastinger må De binde et Content-Length-intervall inn i signaturen.

For én enkelt PUT signerer De ContentLength, slik at S3 håndhever et nøyaktig antall byte. For mer fleksible grenser (et minimums-/maksimumsintervall) bruker De i stedet en forhåndssignert POST-policy, som støtter betingelser med content-length-range.

import { createPresignedPost } from '@aws-sdk/s3-presigned-post';

async function presignPost(client, bucket: string, key: string) {
  return createPresignedPost(client, {
    Bucket: bucket,
    Key: key,
    Conditions: [
      ['content-length-range', 1, 10 * 1024 * 1024], // 1 byte – 10 MB
      ['starts-with', '$Content-Type', 'image/'],
    ],
    Fields: { 'Content-Type': 'image/png' },
    Expires: 120,
  });
}

Opplasting fra nettleseren

Med den forhåndssignerte PUT-URL-en tilgjengelig laster nettleseren opp med en vanlig fetch. Det finnes ingen SDK og ingen AWS-legitimasjon på klienten — bare bytene og en samsvarende Content-Type.

  • Headeren må være lik ContentType-verdien De signerte, ellers returnerer S3 403 SignatureDoesNotMatch.
  • Ikke send Authorization — signaturen ligger i spørringsstrengen.
async function uploadFile(presignedUrl: string, file: File): Promise<void> {
  const res = await fetch(presignedUrl, {
    method: 'PUT',
    headers: { 'Content-Type': file.type },
    body: file,
  });
  if (!res.ok) {
    throw new Error(`Upload failed: ${res.status} ${res.statusText}`);
  }
}

Bekreftelse av opplastingen og lesing av den

Fordi S3 ikke varsler API-et Deres, kaller klienten tilbake etter en vellykket PUT, slik at De kan lagre nøkkelen. For ekstra sikkerhet kan API-et bruke HeadObject for å bekrefte at objektet faktisk finnes, og kontrollere den reelle størrelsen og typen før det stoler på det.

For å levere filen senere kan De enten holde bucket-en privat og utstede en forhåndssignert GET-URL ved behov, eller (for offentlige ressurser) lagre og returnere den offentlige URL-en.

import { GetObjectCommand, HeadObjectCommand } from '@aws-sdk/client-s3';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';

async confirm(userId: string, key: string) {
  const head = await this.s3.client.send(
    new HeadObjectCommand({ Bucket: this.s3.bucket, Key: key }),
  );
  if ((head.ContentLength ?? 0) > 10 * 1024 * 1024) {
    throw new BadRequestException('Object exceeds size limit');
  }
  await this.files.save({ userId, key, size: head.ContentLength });
}

async downloadUrl(key: string) {
  return getSignedUrl(
    this.s3.client,
    new GetObjectCommand({ Bucket: this.s3.bucket, Key: key }),
    { expiresIn: 300 },
  );
}

Kort kontroll

Test forståelsen Deres av hvor tilgangskontrollen ligger i dette mønsteret.

Oppsummering

De har lært hvordan tunge opplastinger kan avlastes til S3 ved hjelp av forhåndssignerte URL-er:

  • Hvorfor: Videresending av byte gjennom API-et sløser med båndbredde og minne, og fører til tidsavbrudd for forespørsler.
  • Hvordan: API-et signerer en kortvarig PutObjectCommand med getSignedUrl; nettleseren gjør PUT direkte til S3.
  • Nøkler: Generer dem alltid på serversiden (UUID + navnerom etter bruker); stol aldri på klientens filnavn.
  • Sikkerhet: Endepunktet for forhåndssignering er tilgangskontrollporten — autentiser, bruk tillatelsesliste for MIME-typer og begrens størrelsen der. Bruk content-length-range i forhåndssignert POST for å la S3 håndheve størrelsen.
  • Etter opplasting: Bekreft med HeadObject, lagre nøkkelen og lever filen senere via forhåndssignerte URL-er for GetObjectCommand.
Gratis å komme i gang

Lær deg TypeScript med en AI-veileder – gratis

Skriv og kjør ekte kode i nettleseren, få umiddelbar hjelp fra en AI-veileder som er tilgjengelig døgnet rundt, og fortsett der du slapp – på nettet eller i appen.

Kurs
20
Leksjoner
76

Ofte stilte spørsmål

Er leksjonen «Direkteopplastinger til S3 med forhåndssignerte URL-er» gratis?

Ja – hele teksten i «Direkteopplastinger til S3 med forhåndssignerte URL-er» er gratis å lese her på nettet. For å øve interaktivt med en innebygd kodeeditor og en AI-veileder som er tilgjengelig døgnet rundt, og for å låse opp resten av NestJS-API-er for virksomhetsbackend-kurset, kan du oppgradere til CoddyKit PRO. Kurset i NestJS-API-er for virksomhetsbackend inneholder totalt 4 leksjoner.

Hva lærer jeg i «Direkteopplastinger til S3 med forhåndssignerte URL-er»?

Avlast tunge opplastinger til objektlagring ved å utstede kortlivede, forhåndssignerte URL-er fra API-et. Du øver på NestJS-API-er for virksomhetsbackend med praktisk kode som du kjører direkte i nettleseren, mens en AI-veileder som er tilgjengelig døgnet rundt, svarer på spørsmålene dine mens du jobber deg gjennom leksjonen.

Trenger jeg erfaring for å begynne med NestJS-API-er for virksomhetsbackend?

Ingen tidligere erfaring er nødvendig. NestJS-API-er for virksomhetsbackend på CoddyKit er lagt opp for både nybegynnere og viderekomne, så De kan begynne her eller helt fra start og lære i Deres eget tempo. Dette er leksjon 3 av 4.

Hvor lang tid tar leksjonen «Direkteopplastinger til S3 med forhåndssignerte URL-er»?

De fleste CoddyKit-leksjoner tar omtrent 5–10 minutter. Hver leksjon er kort og interaktiv, slik at De gjør jevne fremskritt og kan fortsette akkurat der De slapp – både på nettet og i appen.

Kan jeg skrive og kjøre kode i denne NestJS-API-er for virksomhetsbackend-leksjonen?

Ja. Alle NestJS-API-er for virksomhetsbackend-leksjoner har en innebygd kodeeditor, slik at De kan skrive og kjøre ekte kode direkte i nettleseren og få umiddelbar tilbakemelding fra AI – uten lokal konfigurering.

Alle leksjonene i dette kurset

  1. Multipart-opplastinger med Multer-interceptorer
  2. Strømme store svar med StreamableFile
  3. Direkteopplastinger til S3 med forhåndssignerte URL-er
  4. Pipelines for bildebehandling med Sharp
← Tilbake til NestJS-API-er for virksomhetsbackend