Direkteopplastinger til S3 med forhåndssignerte URL-er
Avlast tunge opplastinger til objektlagring ved å utstede kortlivede, forhåndssignerte URL-er fra API-et.
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
PUTav 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 S3403 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
PutObjectCommandmedgetSignedUrl; nettleseren gjørPUTdirekte 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-rangei 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 forGetObjectCommand.
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
- Multipart-opplastinger med Multer-interceptorer
- Strømme store svar med StreamableFile
- Direkteopplastinger til S3 med forhåndssignerte URL-er
- Pipelines for bildebehandling med Sharp