AWS SDK v3 Node.js Streams Shouldn't Be This Hard (Here's the Fix)

AWS SDK v3 made working with streams in Node.js a lot more difficult than it was in v2. This article shows you how to simplify your migration from AWS SDK v2 to v3.
Why Node Streams Break in AWS SDK v3
In AWS SDK v3, the
StreamingBlobPayloadOutputTypes
type
is used to represent streams that can be returned in both browser and Node.js environments.
type StreamingBlobPayloadOutputTypes = | NodeJsRuntimeStreamingBlobPayloadOutputTypes | BrowserRuntimeStreamingBlobPayloadOutputTypes;
When you’re working in an application that will only be run in Node.js, this union type is painful to deal with.
For example, consider this code that uses the GetObjectCommand
to download a file from S3:
import { GetObjectCommand, S3Client } from "@aws-sdk/client-s3";
export async function processFromS3(bucket: string, key: string) { const s3 = new S3Client({});
const result = await s3.send( new GetObjectCommand({ Bucket: bucket, Key: key, }), );
const stream = result.Body; if (stream) { // Does not work // Property 'destroy' does not exist on type 'StreamingBlobPayloadOutputTypes'. // Property 'destroy' does not exist on type 'Blob & SdkStreamMixin'.ts(2339) stream.destroy(); }}
This can be frustrating, especially when you’re sure you’re dealing with a Node.js stream.
Fixing It with NodeJsClient
Type Cast
Luckily, there’s an underdocumented way to tell AWS SDK v3 what environment you’re running in, so you don’t have to
manually narrow stream types yourself. The
NodeJsClient
type
tells AWS SDK v3 to use Node.js-specific stream APIs when returning streams from these clients.
import { GetObjectCommand, S3Client } from "@aws-sdk/client-s3";import { type NodeJsClient } from "@smithy/types";
export async function processFromS3(bucket: string, key: string) { const s3 = new S3Client({}) as NodeJsClient<S3Client>; // cast to NodeJsClient
const result = await s3.send( new GetObjectCommand({ Bucket: bucket, Key: key, }), );
const stream = result.Body; // automatically type-narrowed to SdkStream<IncomingMessage> if (stream) { // No longer flags a type error stream.destroy(); }}
Save yourself some hassle! Just cast to NodeJsClient<Client>
and move on 🎉