typescript 95 lines · 3 tabs

Multipart upload streaming (busboy)

Shared by codesnips Jan 2026
3 tabs
import { Readable } from "node:stream";
import { S3Client } from "@aws-sdk/client-s3";
import { Upload } from "@aws-sdk/lib-storage";

const s3 = new S3Client({ region: process.env.AWS_REGION });
const BUCKET = process.env.UPLOAD_BUCKET!;

export interface UploadResult {
  key: string;
  location: string;
  bytes: number;
}

export async function streamToS3(
  key: string,
  body: Readable,
  contentType: string,
): Promise<UploadResult> {
  let bytes = 0;
  body.on("data", (chunk: Buffer) => {
    bytes += chunk.length;
  });

  const upload = new Upload({
    client: s3,
    params: { Bucket: BUCKET, Key: key, Body: body, ContentType: contentType },
    partSize: 8 * 1024 * 1024, // 8 MB parts
    queueSize: 4, // upload up to 4 parts concurrently
    leavePartsOnError: false,
  });

  const out = await upload.done();
  return { key, location: out.Location ?? "", bytes };
}
3 files · typescript Explain with highlit

This snippet shows how to accept large file uploads in an Express service without buffering entire files into memory or writing them to temporary disk first. The core idea is that busboy parses a multipart/form-data request as a stream of events, handing back a readable stream per file the moment its headers arrive. That stream can be piped straight into the AWS SDK's Upload helper, so bytes flow from the socket into S3 in fixed-size parts while the client is still uploading.

In s3-upload.ts, the streamToS3 function wraps @aws-sdk/lib-storage's Upload, which internally performs a multipart upload: it reads from the source stream, splits it into partSize chunks, and uploads parts concurrently up to queueSize. Passing the file's own readable stream as Body means the SDK applies backpressure — if S3 is slow, the SDK stops pulling, the file stream pauses, and busboy in turn pauses the request socket. This is why memory stays flat regardless of file size, which is the whole point of streaming over multer-style disk staging.

In upload.middleware.ts, createUploadHandler builds the busboy instance from the request headers and applies limits for file size and count so a malicious client cannot exhaust resources. Each file event yields the field name, the readable stream, and metadata like filename and mimeType. The handler collects a promise per file and, critically, listens for the limits event on each file stream: when a file exceeds fileSize, busboy emits limit and the stream must be drained or destroyed, otherwise the request hangs. The filesLimit and error events are forwarded to reject the aggregate.

In routes.ts, the route awaits Promise.all of all file uploads only after busboy emits finish, ensuring every part has completed before responding. A pitfall worth noting: responding early or throwing without consuming the stream leaves the socket half-read, so errors destroy the busboy stream to unblock the connection. This pattern suits APIs handling video, backups, or datasets where files routinely exceed available RAM, trading the simplicity of buffered parsing for constant memory and true streaming throughput.


Related snips

typescript
export interface RetryOptions {
  retries: number;
  baseMs: number;
  maxMs: number;
  signal?: AbortSignal;
  onRetry?: (attempt: number, delay: number, err: unknown) => void;

Exponential backoff with jitter for retries

typescript reliability retry
by codesnips 2 tabs
ruby
require "csv"

class PeopleCsvStream
  include Enumerable

  HEADERS = %w[id full_name email signed_up_at plan].freeze

Resilient CSV Export as a Streamed Response

rails performance streaming
by codesnips 3 tabs
typescript
export type Settled<R> =
  | { status: 'fulfilled'; value: R }
  | { status: 'rejected'; reason: unknown };

export interface ConcurrencyOptions {
  limit: number;

Simple concurrency limiter for batch operations

node concurrency async
by codesnips 2 tabs
typescript
import axios, { AxiosError } from 'axios'
import { v4 as uuidv4 } from 'uuid'

const api = axios.create({
  baseURL: import.meta.env.VITE_API_URL || 'http://localhost:3000/api/v1',
  timeout: 15000,

Axios API client with interceptors

react axios api
by Maya Patel 1 tab
typescript
import { create } from 'zustand'
import { persist } from 'zustand/middleware'

interface FilterState {
  search: string
  category: string | null

Zustand for lightweight state management

react zustand state-management
by Maya Patel 2 tabs
typescript
import { randomBytes, createHash } from "crypto";
import jwt from "jsonwebtoken";
import { RefreshTokenStore } from "./store";

const ACCESS_SECRET = process.env.ACCESS_SECRET!;
const ACCESS_TTL = "15m";

JWT access + refresh token rotation (conceptual)

security node jwt
by codesnips 3 tabs

Share this code

Here's the card — post it anywhere.

Multipart upload streaming (busboy) — share card
Link copied