avatarcodebrew.blog

Summary

The provided content offers guidance on uploading files to an AWS S3 bucket using JavaScript and TypeScript, including code snippets and prerequisites for AWS setup.

Abstract

The article provides a comprehensive tutorial on how to upload files to an AWS S3 bucket using JavaScript and TypeScript. It begins by listing the prerequisites, such as having an AWS account, an S3 bucket, and an IAM user with S3 access and generated access keys. The author includes code snippets for both JavaScript and TypeScript, demonstrating how to upload text files and local files or images, and how to set up an environment for file uploads. The article also covers alternative methods for uploading files, such as using pre-signed URLs or the AWS CLI. Additionally, it guides readers on integrating S3 uploads into a backend service using Express, Busboy, and the AWS SDK for JavaScript. The author emphasizes the importance of using proper environment variable handlers and notes that the AWS access keys provided in the examples will be non-functional by the time of publication.

Opinions

  • The author suggests that using a proper environment variable handler is important for managing AWS credentials securely.
  • The article implies that the AWS JavaScript SDK is a suitable tool for interacting with S3 from both JavaScript and TypeScript applications.
  • The author provides a subjective recommendation to use pre-signed URLs for file uploads to avoid processing on the API side, indicating a preference for this method in certain scenarios.
  • The use of Busboy for parsing multipart/form-data requests in an Express server is presented as an effective solution for handling file uploads in a backend service.
  • The author encourages readers to use their own AWS Access Keys and reminds them that the keys provided in the article will not be functional, highlighting the need for personalized security practices.
  • By offering a full script example and mentioning the ease of running it with node upload.js or pnpm tsx upload.ts, the author conveys that the provided code is ready to use and easy to implement.

AWS S3 Bucket Uploads with JavaScript and TypeScript

Short scripts & snippets for uploading to an AWS S3 Bucket with JavaScript or TypeScript

This article includes short snippets and guides on uploading files with JavaScript & TypeScript for your web apps or scripts.

Any code blocks or code sandboxes will be included in their respective sections or at the end of the article.

Pre-requisites

This guide will not be going over how to create an S3 Bucket or how to create and generate an AWS Access Key. Please refer to external guides.

  • An AWS Account
  • An S3 Bucket created
  • An IAM user with S3 Access and generated access keys

Alternatives

Some other ways you could upload files to S3 without using JS/TS S3 SDK:

  • Create a pre-signed URL, that your frontend can directly use to upload files to S3, cutting out the processing on your API side
  • Using AWS CLI V2 for simple local upload using your terminal
  • Simply uploading via the AWS S3 console

Uploading with JavaScript

Install required packages with your choice of package manager, we’re using pnpm for this article.

pnpm add @aws-sdk/client-s3

Prepare your environment variables, you should use a proper env handler.

// Import your env using dotenv for example 
const env = {
  BUCKET_NAME: "YOUR_BUCKET_NAME",
  AWS_ACCESS_KEY_ID: "XXXXXXXXXXXX",
  AWS_SECRET_ACCESS_KEY: "XXXXXXXXXXXXXXXXX",
};

Start writing your upload script boilerplate:

const { PutObjectCommand, S3Client } = require("@aws-sdk/client-s3");
const fs = require("fs/promises");

// These are real values examples
const env = {
  BUCKET_NAME: "chicken-nuggets-bucket",
  AWS_ACCESS_KEY_ID: "AKIAZ6COO6JQLFFDDWDN",
  AWS_SECRET_ACCESS_KEY: "MCF8adNBNZWA6OurU5EUL95mx8OqZl5r8PX+aZhy",
};

// Create a new reusable S3 Client
const client = new S3Client({
  credentials: {
    accessKeyId: env.AWS_ACCESS_KEY_ID,
    secretAccessKey: env.AWS_SECRET_ACCESS_KEY,
  },
});

// Our main function to be run
const main = async () => {
  ...
};

main().catch((err) => console.error(err))

Uploading text file

// Extracted into a function
const uploadTextAsFile = async (key, body) => {
  const command = new PutObjectCommand({
    Bucket: env.BUCKET_NAME,
    Key: `${key}.txt`,
    Body: body,
  });

  await client.send(command);
};

// Usage
const main = async () => {
  await uploadTextAsFile("hello", "World!");
};

Uploading local file or image

// Take note of the extra import here, no need to install it
const fs = require("fs/promises");

// Extracted into a function
const uploadFile = async (fileName) => {
  // Try and have a dedicated folder just for files to be uploaded
  const selectedFile = await fs.readFile("./uploads/" + fileName)
  const command = new PutObjectCommand({
    Bucket: env.BUCKET_NAME,
    Key: fileName,
    Body: selectedFile,
  });

  await client.send(command);
};

// File upload
const main = async () => {
  await uploadFile("my-file.json");
  await uploadFile("my-image.png");
};

You can extend the upload file function to check if the file exists before uploading or that the file name provided includes a file extension.

Full script can be run just by node upload.js

// # upload.js

const { PutObjectCommand, S3Client } = require("@aws-sdk/client-s3");
const fs = require("fs/promises");

// These are real values examples
const env = {
  BUCKET_NAME: "chicken-nuggets-bucket",
  AWS_ACCESS_KEY_ID: "AKIAZ6COO6JQLFFDDWDN",
  AWS_SECRET_ACCESS_KEY: "MCF8adNBNZWA6OurU5EUL95mx8OqZl5r8PX+aZhy",
};

const client = new S3Client({
  credentials: {
    accessKeyId: env.AWS_ACCESS_KEY_ID,
    secretAccessKey: env.AWS_SECRET_ACCESS_KEY,
  },
});

const uploadTextAsFile = async (key, body) => {
  const command = new PutObjectCommand({
    Bucket: env.BUCKET_NAME,
    Key: `${key}.txt`,
    Body: body,
  });

  await client.send(command);
};

const uploadFile = async (fileName) => {
  // Try and have a dedicated folder just for files to be uploaded
  const selectedFile = await fs.readFile("./uploads/" + fileName)
  const command = new PutObjectCommand({
    Bucket: env.BUCKET_NAME,
    Key: fileName,
    Body: selectedFile,
  });

  await client.send(command);
};

const main = async () => {
  await uploadTextAsFile("hello", "World!");
  await uploadFile("my-file.json");
  await uploadFile("my-image.png");
};

main().catch((err) => console.error(err))

Uploading with TypeScript

For TypeScript, we will need to install extra packages to get things going

pnpm add -D tsx typescript

// # upload.ts

import { PutObjectCommand, S3Client } from "@aws-sdk/client-s3";
import fs from "fs/promises";

// These are real values examples
const env = {
  BUCKET_NAME: "chicken-nuggets-bucket",
  AWS_ACCESS_KEY_ID: "AKIAZ6COO6JQLFFDDWDN",
  AWS_SECRET_ACCESS_KEY: "MCF8adNBNZWA6OurU5EUL95mx8OqZl5r8PX+aZhy",
} as const;

const client = new S3Client({
  credentials: {
    accessKeyId: env.AWS_ACCESS_KEY_ID,
    secretAccessKey: env.AWS_SECRET_ACCESS_KEY,
  },
});

const uploadTextAsFile = async (key: string, body: string) => {
  const command = new PutObjectCommand({
    Bucket: env.BUCKET_NAME,
    Key: `${key}.txt`,
    Body: body,
  });

  await client.send(command);
};

const uploadFile = async (fileName: string) => {
  // Try and have a dedicated folder just for files to be uploaded
  const selectedFile = await fs.readFile("./uploads/" + fileName);
  const command = new PutObjectCommand({
    Bucket: env.BUCKET_NAME,
    Key: fileName,
    Body: selectedFile,
  });

  await client.send(command);
};

const main = async () => {
  await uploadTextAsFile("hello", "World!");
  await uploadFile("my-file.json");
  await uploadFile("my-image.png");
};

main().catch((err) => console.error(err));

There is not much difference between the JS and TS versions for this case, but to get the script started simply run pnpm tsx upload.ts

Use as a Backend Service

The above use cases may be better for local scripts you may use yourself, now you may want it as part of an API for your latest side project or successful business site that you are running.

Here is a quick guide that uses Express:

pnpm add express busboy @aws-sdk/client-s3

pnpm add -D typescript tsx @types/node @types/express @types/busboy

// # api.ts

import express, { type Request, type Response } from "express";
import { PutObjectCommand, S3Client } from "@aws-sdk/client-s3";
import busboy, { FileInfo } from "busboy";

type RawFile = {
  fileName: string;
  info: FileInfo;
  data: Buffer;
};

// You should be using something like dotenv instead
const env = {
  PORT: 3000,
  BUCKET_NAME: "chicken-nuggets-bucket",
  AWS_ACCESS_KEY_ID: "AKIAZ6COO6JQLFFDDWDN",
  AWS_SECRET_ACCESS_KEY: "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",
} as const;

const app = express();
const port = env.PORT || 3000;

const client = new S3Client({
  credentials: {
    accessKeyId: env.AWS_ACCESS_KEY_ID,
    secretAccessKey: env.AWS_SECRET_ACCESS_KEY,
  },
});

app.post("/upload", async (req: Request, res: Response) => {
  const files = await parseMultipartReq(req);
  for (const file of files) {
    await uploadFile(file.info.filename ?? file.fileName, file.data);
  }
  res.status(200).json({ message: "Files uploaded!" });
});

app.listen(port, () => {
  console.log(`[server]: Server is running at http://localhost:${port}`);
});

// Parses a request and returns the files
const parseMultipartReq = async (req: Request) => {
  const bb = busboy({ headers: req.headers });

  // Pipe request stream into busboy
  req.pipe(bb);

  return new Promise<RawFile[]>((resolve) => {
    const files: RawFile[] = [];

    // Does a loop throught the files submitted
    bb.on("file", (name, stream, info) => {
      const fileChunks: Buffer[] = [];
      stream.on("data", (chunk) => fileChunks.push(chunk));
      stream.on("end", () =>
        files.push({ fileName: name, data: Buffer.concat(fileChunks), info })
      );
    });

    bb.on("close", () => resolve(files));
  });
};

const uploadFile = async (fileName: string, fileBuffer: Buffer) => {
  const command = new PutObjectCommand({
    Bucket: env.BUCKET_NAME,
    Key: fileName,
    Body: fileBuffer,
  });

  await client.send(command);
};

As for running the API, just run pnpm tsx api.ts once running, you can then make a POST request to the http://localhost:5000 with form data.

Any Access Keys you see in the article will no longer be usable by the time the article has been published, you should be using your Access Keys anyway.

Feel free to leave a comment if you require any assistance or if you’re having trouble with external links.

Helpful/Related resources

AWS
JavaScript
Typescript
Coding
Software
Recommended from ReadMedium