NestJS-API-er for virksomhetsbackend · leksjon

Strømme store svar med StreamableFile

Server store nedlastinger effektivt med StreamableFile og lesbare Node-strømmer for å unngå buffering.

Leksjon 2 av 413 trinn

Strømme store svar med StreamableFile er en gratis leksjon i NestJS-API-er for virksomhetsbackend på CoddyKit. Dette er leksjon 2 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.

Bufringsproblemet

Når en kontroller returnerer en stor fil, er den naive fremgangsmåten å lese hele filen inn i minnet og sende den tilbake:

  • fs.readFileSync('huge.zip') laster hver eneste byte inn i RAM før den første byten når klienten.
  • En eksport på 2 GB som leveres til 50 samtidige brukere, kan fylle opp heapen og krasje prosessen.

Strømming løser dette: Les filen i små deler og send dem videre til svaret etter hvert som de kommer, slik at minnebruken holder seg flat uansett filstørrelse.

Hva StreamableFile er

NestJS leverer klassen StreamableFile. Du pakker en Node.js-Readable-strøm (eller en Buffer) inn i den og returnerer den fra en handler i kontrolleren.

  • Nest oppdager returverdien StreamableFile og sender den underliggende strømmen videre til HTTP-svaret for deg.
  • Den fungerer med både Express- og Fastify-adaptere uten at du trenger å bruke res.pipe() manuelt.

Dette holder handleren din deklarativ, samtidig som dataene strømmes del for del.

import { Controller, Get, StreamableFile } from '@nestjs/common';
import { createReadStream } from 'fs';
import { join } from 'path';

@Controller('files')
export class FilesController {
  @Get('report')
  getReport(): StreamableFile {
    const file = createReadStream(join(process.cwd(), 'report.pdf'));
    return new StreamableFile(file);
  }
}

Slik flyter en Readable-strøm

Under panseret sender createReadStream ut 'data'-hendelser med små Buffer-biter (64 KB som standard). Responsen bruker dem én om gangen.

Her er en enkel Node-demonstrasjon av lesing i biter uten noe rammeverk — legg merke til at minnet aldri inneholder hele filen samtidig:

import { Readable } from 'stream';

// Simulate a large source as a stream of chunks
async function* generateChunks() {
  for (let i = 0; i < 5; i++) {
    yield `chunk-${i} `;
  }
}

const stream = Readable.from(generateChunks());

stream.on('data', (chunk: Buffer | string) => {
  console.log('received:', chunk.toString().trim());
});
stream.on('end', () => console.log('done streaming'));

Angi Content-Type og filnavn

Som standard vet ikke nettleseren hva strømmen inneholder. Send alternativer til StreamableFile slik at Nest setter riktige headere:

  • type angir Content-Type-headeren.
  • disposition angir Content-Disposition, slik at nettleseren laster ned filen med et filnavn i stedet for å vise den direkte.
import { Controller, Get, StreamableFile } from '@nestjs/common';
import { createReadStream } from 'fs';
import { join } from 'path';

@Controller('files')
export class FilesController {
  @Get('invoice')
  getInvoice(): StreamableFile {
    const file = createReadStream(join(process.cwd(), 'invoice.pdf'));
    return new StreamableFile(file, {
      type: 'application/pdf',
      disposition: 'attachment; filename="invoice.pdf"',
    });
  }
}

Headere via @Header kontra StreamableFile-alternativer

De kan også angi headere med @Header()-dekoratøren, men bruk av begge deler kan føre til konflikter. Foretrekk alternativene i StreamableFile, fordi Nest bruker dem konsekvent med begge adapterne.

  • Bruk @Header('Content-Type', ...) bare når verdien er statisk og kjent ved kompilering.
  • Bruk alternativene type/disposition i StreamableFile når verdien beregnes per forespørsel (for eksempel et dynamisk filnavn).
import { Controller, Get, Header, StreamableFile } from '@nestjs/common';
import { createReadStream } from 'fs';

@Controller('exports')
export class ExportsController {
  @Get('static')
  @Header('Content-Type', 'text/csv')
  @Header('Content-Disposition', 'attachment; filename="data.csv"')
  download(): StreamableFile {
    return new StreamableFile(createReadStream('data.csv'));
  }
}

Strømming av generert innhold (ingen fil på disk)

StreamableFile er ikke begrenset til filer på disk. Alle Readable-strømmer fungerer — også data som genereres underveis. Dette er ideelt for store CSV-eksporter som bygges rad for rad fra en databasepeker.

Nedenfor genererer en generator CSV-linjer på en lazy måte, slik at hele datasettet aldri materialiseres i minnet samtidig.

import { Controller, Get, StreamableFile, Header } from '@nestjs/common';
import { Readable } from 'stream';

@Controller('exports')
export class CsvExportController {
  @Get('users.csv')
  @Header('Content-Type', 'text/csv')
  exportUsers(): StreamableFile {
    async function* rows() {
      yield 'id,name\n';
      for (let i = 1; i <= 100000; i++) {
        yield `${i},user_${i}\n`;
      }
    }
    return new StreamableFile(Readable.from(rows()));
  }
}

Mottrykk: Hvorfor strømming holder minnebruken trygg

Mottrykk er mekanismen som gjør strømming trygg. Når klienten (eller nettverket) er tregt, signaliserer den skrivbare siden til den lesbare siden at den skal sette produksjonen av biter på pause.

  • En rask disketøyingsoperasjon kombinert med en treg klient fører ikke til at gigabyte hoper seg opp i minnet.
  • Node sin pipe() (som brukes internt av Nest) håndterer dette automatisk — kilden settes på pause og gjenopptas etter behov.

Dette er nettopp grunnen til at De bør returnere en strøm i stedet for en enorm Buffer.

import { Writable, Readable } from 'stream';

const source = Readable.from(['a', 'b', 'c', 'd', 'e']);

const slowSink = new Writable({
  write(chunk, _enc, cb) {
    console.log('wrote:', chunk.toString());
    setTimeout(cb, 10); // simulate slow consumer -> triggers backpressure
  },
});

source.pipe(slowSink);
slowSink.on('finish', () => console.log('all chunks flushed safely'));

Håndtering av strømfeil

Hvis den underliggende strømmen feiler etter at headerne er sendt, kan De ikke lenger sende en JSON-feilkropp. StreamableFile tilbyr en feilhåndterer slik at De kan logge feilen og avslutte på en ryddig måte.

  • Bruk getStream().on('error', ...) eller alternativet setErrorHandler()/feilhåndtering for å reagere på lesefeil.
  • Uten dette kan en manglende fil føre til at tilkoblingen blir hengende, eller at forespørselen krasjer.
import { Controller, Get, StreamableFile, Logger } from '@nestjs/common';
import { createReadStream } from 'fs';

@Controller('files')
export class SafeFilesController {
  private readonly logger = new Logger(SafeFilesController.name);

  @Get('archive')
  getArchive(): StreamableFile {
    const stream = createReadStream('archive.zip');
    const file = new StreamableFile(stream);
    file.setErrorHandler((err, response) => {
      this.logger.error(`Stream failed: ${err.message}`);
      response.statusCode = 404;
      response.end('File not available');
    });
    return file;
  }
}

Strømming fra objektlagring (S3)

I bedriftsapplikasjoner ligger filen vanligvis i S3 eller en annen objektlagring, ikke på lokal disk. S3 SDK returnerer en lesbar strøm for objektkroppen — send den direkte til StreamableFile.

  • Ingen midlertidig fil og ingen full nedlasting til API-serverens minne.
  • Byte flyter fra S3 → API-et Deres → klienten som gjennom en relé.
import { Controller, Get, Param, StreamableFile } from '@nestjs/common';
import { S3Client, GetObjectCommand } from '@aws-sdk/client-s3';
import { Readable } from 'stream';

@Controller('media')
export class MediaController {
  private s3 = new S3Client({ region: 'eu-central-1' });

  @Get(':key')
  async download(@Param('key') key: string): Promise<StreamableFile> {
    const obj = await this.s3.send(
      new GetObjectCommand({ Bucket: 'my-bucket', Key: key }),
    );
    return new StreamableFile(obj.Body as Readable);
  }
}

Tilgang til det rå svaret med @Res({ passthrough })

Noen ganger trenger De det rå responsobjektet for å angi statuskoder eller ekstra headere, samtidig som Nest fortsatt skal la StreamableFile strømme gjennom. Bruk @Res({ passthrough: true }) slik at Nest beholder kontrollen over responsens livssyklus.

  • Uten passthrough: true blir De selv ansvarlig for å avslutte responsen når De injiserer @Res(), og det fungerer ikke lenger automatisk å returnere en StreamableFile.
import { Controller, Get, Res, StreamableFile } from '@nestjs/common';
import type { Response } from 'express';
import { createReadStream, statSync } from 'fs';

@Controller('files')
export class RangeController {
  @Get('video')
  getVideo(@Res({ passthrough: true }) res: Response): StreamableFile {
    const { size } = statSync('movie.mp4');
    res.set({ 'Content-Length': size, 'Accept-Ranges': 'bytes' });
    return new StreamableFile(createReadStream('movie.mp4'));
  }
}

Transformering av en strøm underveis

De kan kjede transformeringer før strømmen sendes til StreamableFile. Et vanlig tilfelle er å komprimere en stor eksport med gzip, slik at mindre data sendes over nettet — fortsatt bit for bit.

Her er en frittstående demonstrasjon av piping gjennom en transformering uten noen server:

import { Readable, Transform } from 'stream';

const upper = new Transform({
  transform(chunk, _enc, cb) {
    cb(null, chunk.toString().toUpperCase());
  },
});

const source = Readable.from(['hello ', 'streamed ', 'world']);

source.pipe(upper).on('data', (c) => console.log(c.toString()));
upper.on('end', () => console.log('transform complete'));

Kort kontroll

Test forståelsen Deres av den grunnleggende beslutningen bak StreamableFile.

Oppsummering

Viktigste punkter for strømming av store responser i NestJS:

  • Returner new StreamableFile(readable) i stedet for å bufre hele filer i minnet.
  • Pakk inn enhver Readable: en createReadStream, en S3-objektkropp eller en generatorbasert strøm.
  • Angi type og disposition (eller @Header) slik at klientene får riktig MIME-type og filnavn for nedlastingen.
  • Mottrykk holder minnebruken stabil når klientene er trege — dette er hele grunnen til å strømme data.
  • Knytt til setErrorHandler() for lesefeil som oppstår etter at headerne er sendt.
  • Bruk @Res({ passthrough: true }) når De trenger tilgang til den rå responsen, samtidig som Nest beholder pipingen.
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 «Strømme store svar med StreamableFile» gratis?

Ja – hele teksten i «Strømme store svar med StreamableFile» 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 «Strømme store svar med StreamableFile»?

Server store nedlastinger effektivt med StreamableFile og lesbare Node-strømmer for å unngå buffering. 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 2 av 4.

Hvor lang tid tar leksjonen «Strømme store svar med StreamableFile»?

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