Uploading files to Google Cloud Storage with Remix.run
Tags: googlecloud, remix
Published: 2022-02-25 07:37:28

The [Remix.run documentation](https://remix.run/docs/en/v1/api/remix#unstable_parsemultipartformdata-node) shows us how to upload files directly to the disk of the application, although you have to look around a bit.

The code to upload a file to the `/public/uploads` directory looks like this. 

```react
// ./app/routes/upload.tsx

import {
  ActionFunction,
  Form,
  unstable_createFileUploadHandler,
  unstable_parseMultipartFormData,
} from 'remix';

export const fileUploadHandler = unstable_createFileUploadHandler({
  directory: './public/uploads',
  file: ({ filename }) => filename,
});

export const action: ActionFunction = async ({ request }) => {
  const formData = await unstable_parseMultipartFormData(request, fileUploadHandler);
  console.log(formData.get('upload')); // will return the filename

  return {};
};

const Upload = () => {
  return (
    <Form method="post" encType="multipart/form-data">
      <input type="file" name="upload" />
      <button type="submit">upload</button>
    </Form>
  );
};

export default Upload;
```

The documentation also has an example to stream your upload to [Cloudinary](https://remix.run/docs/en/v1/api/remix#custom-uploadhandler), with a custom `uploadHandler`.

But, since I'm using the [Google Cloud Platform](https://cloud.google.com), I want my files to be stored in a [Cloud Storage bucket](https://cloud.google.com/storage).

Therefor, I use the [@google-cloud/storage](https://www.npmjs.com/package/@google-cloud/storage) package.

My route now looks like this

```react
// ./app/routes/upload.tsx

import { ActionFunction, Form, unstable_parseMultipartFormData, useActionData } from 'remix';
import { cloudStorageUploaderHandler } from '~/services/upload-handler.server';

export const action: ActionFunction = async ({ request }) => {
  const formData = await unstable_parseMultipartFormData(request, cloudStorageUploaderHandler);
  const filename = formData.get('upload');

  return { filename };
};

const Upload = () => {
  const actionData = useActionData();

  if (actionData && actionData.filename) {
    return <>Upload successful.</>;
  }

  return (
    <Form method="post" encType="multipart/form-data">
      <input type="file" name="upload" />
      <button type="submit">upload</button>
    </Form>
  );
};

export default Upload;
```

I created a service that should only run server-side in `./app/services/uploader-handler.server.ts` which looks like this.
```typescript
import { Readable } from 'stream';
import { Storage } from '@google-cloud/storage';
import { UploadHandler } from 'remix';

const uploadStreamToCloudStorage = async (fileStream: Readable, fileName: string) => {
  const bucketName = 'YOUR_BUCKET_NAME';

  // Create Cloud Storage client
  const cloudStorage = new Storage();

  // Create a reference to the file.
  const file = cloudStorage.bucket(bucketName).file(fileName);

  async function streamFileUpload() {
    fileStream.pipe(file.createWriteStream()).on('finish', () => {
      // The file upload is complete
    });

    console.log(`${fileName} uploaded to ${bucketName}`);
  }

  streamFileUpload().catch(console.error);

  return fileName;
};

export const cloudStorageUploaderHandler: UploadHandler = async ({
  filename,
  stream: fileStream,
}) => {
  return await uploadStreamToCloudStorage(fileStream, filename);
};
```

Et voila, the file from the form, is now directly streamed to Google Cloud Storage.