DEV Community 👩‍💻👨‍💻

cryptic
cryptic

Posted on

Uploading files in Remix to a S3 compatible service

Remix is a new react-based framework designed to be a full stack application. With many applications, you will need to be able to store files, sometimes in an S3 compatible service. Here is how I was able to accomplish this. I took heavy influence from this dev.to article.

Create a file named uploader-handler.server.ts with the following contents:

import { s3Client } from './s3.server';
import type { UploadHandler } from '@remix-run/node';
import type { PutObjectCommandInput } from '@aws-sdk/client-s3';
import { GetObjectCommand } from '@aws-sdk/client-s3';
import { PutObjectCommand } from '@aws-sdk/client-s3';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';

const uploadStreamToS3 = async (data: AsyncIterable<Uint8Array>, key: string, contentType: string) => {
  const BUCKET_NAME = "my_bucket_name";

  const params: PutObjectCommandInput = {
    Bucket: BUCKET_NAME,
    Key: key,
    Body: await convertToBuffer(data),
    ContentType: contentType,
  };

  await s3Client.send(new PutObjectCommand(params));

  let url = await getSignedUrl(s3Client, new GetObjectCommand({
    Bucket: BUCKET_NAME,
    Key: key,
  }), { expiresIn: 15 * 60 });

  console.log(url);

  return key;
}

// The UploadHandler gives us an AsyncIterable<Uint8Array>, so we need to convert that to something the aws-sdk can use. 
// Here, we are going to convert that to a buffer to be consumed by the aws-sdk.
async function convertToBuffer(a: AsyncIterable<Uint8Array>) {
  const result = [];
  for await (const chunk of a) {
    result.push(chunk);
  }
  return Buffer.concat(result);
}

export const s3UploaderHandler: UploadHandler = async ({ filename, data, contentType }) => {
  return await uploadStreamToS3(data, filename!, contentType);
}
Enter fullscreen mode Exit fullscreen mode

Next, you will need to create the actual route to be able to upload a file. I have the following file: ~/routes/api/storage/upload.tsx with the following contents

import type { ActionFunction } from "@remix-run/node";
import { unstable_parseMultipartFormData } from "@remix-run/node";
import { auth } from "~/server/auth.server";
import { s3UploaderHandler } from "~/server/uploader-handler.server";

export const action: ActionFunction = async ({ request }) => {
  await auth.isAuthenticated(request, { failureRedirect: '/login' });

  const formData = await unstable_parseMultipartFormData(request, s3UploaderHandler);

  const fileName = formData.get('upload');

  return {
    filename: fileName,
  }
}
Enter fullscreen mode Exit fullscreen mode

Now that you have the supporting files in place, let's upload a file.

<Form method="post" action={'/api/storage/upload'} encType="multipart/form-data">
    <Input type="file" name="upload" />
    <Button type="submit">Upload</Button>
</Form>
Enter fullscreen mode Exit fullscreen mode

There you have it!

Version of sdks used:

  • @remix-run/node: 1.6.5
  • @remix-run/react: 1.6.5
  • @aws-sdk/client-s3: 3.145.0
  • @aws-sdk/s3-request-presigner: 3.145.0

Top comments (0)

🌱 DEV runs on 100% open source code that we started called Forem.

 
You can contribute to the codebase or host your own.