Uploading files to Google Cloud Storage with Remix.run
Tim Vermaercke
Posted on February 25, 2022
The Remix.run documentation 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.
// ./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, with a custom uploadHandler
.
But, since I'm using the Google Cloud Platform, I want my files to be stored in a Cloud Storage bucket.
Therefor, I use the @google-cloud/storage package.
My route now looks like this
// ./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.
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.
Posted on February 25, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.