Using Superflare
File Storage
Superflare provides a simple interface for working with R2 storage. You can use this to store and retrieve files from Cloudflare Workers.
Getting Started
To interact with R2 storage, import the storage
utility from Superflare:
import { storage } from "superflare";
export async function action() {
const file = "...";
const key = "my-file.txt";
const r2Object = await storage().put(key, file);
// Later:
const file = await storage().get(key);
}
By default, Superflare will use the default
storage disk and underlying R2 storage bucket defined in the storage#default
property of your superflare.config.ts
file.
You can override this by passing a different disk name to the storage
function:
import { storage } from "superflare";
export async function action() {
await storage("my-disk").put(key, file);
}
Handling file uploads
If your application accepts file uploads, you will likely have an endpoint, like a Remix action, which handles an incoming request.
The simplest approach to handling file uploads is to use the FormData API to parse the request body with await request.formData()
and use the storage().put(name, formData.get('file'))
method to store the file in your R2 bucket.
However, this approach requires you to read the entire file into memory before storing it. This can be problematic if you are uploading large files:
import { storage } from "superflare";
export async function action({ request }) {
// ⚠️ Entire file is loaded into memory!
const formData = await request.formData();
const file = formData.get("file");
const key = "my-file.txt";
const r2Object = await storage().put(key, file);
}
Instead, a better option is to stream file uploads directly to R2. Superflare provides a parseMultipartFormData
utility, inspired by Remix, to make parsing multipart form data requests easier:
import { storage, parseMultipartFormData } from "superflare";
export async function action({ request }) {
const formData = await parseMultipartFormData(
request,
async ({ name, filename, stream, data }) => {
// This is the name of your HTML file input
if (name === "file") {
const r2Object = await storage().put(filename, stream);
return r2Object.key;
}
// Support other non-file fields
return data;
}
);
const url = storage.url(formData.get("file"));
// ...
}
Uploading files with random names
Sometimes you may want to upload a file with a random name to prevent conflicts between other uploads. You can use the storage().putRandom(input)
method to do this:
const r2Object = await storage().putRandom(file, {
// Optional extension
extension: filename?.split(".").pop(),
// Optional prefix
prefix: "avatars",
});
By default, no extension or prefix will be added to the file name. You can override this by passing an extension
or prefix
option to the putRandom
method. Prefixes and filenames are separated by a forward slash (/
) similar to a folder structure.
Serving public files
By default, Superflare does not serve your bucket contents to the public. However, you can mark a disk as public by setting the publicPath
property to a public route in your superflare.config.ts
file:
// superflare.config.ts
import { defineConfig } from "superflare";
export default defineConfig((ctx) => {
return {
// ...
storage: {
default: {
binding: ctx.env.MY_BUCKET,
publicPath: "/storage/files",
},
},
};
});
File URLs
You can use the storage().url()
method to get a URL to a file in your bucket if you have defined a public path:
const url = storage().url("my-file.txt");
If your bucket does not have a public path, the storage().url()
will return null
.
servePublicPathFromStorage
Superflare provides a helpful utility servePublicPathFromStorage
to serve public files from your app by proxying requests to an R2 bucket.
You can import this utility in a route handler and pass it the current pathname to serve:
// app/routes/storage.$.ts
import { type LoaderArgs } from "@remix-run/cloudflare";
import { servePublicPathFromStorage } from "superflare";
export async function loader({ request }: LoaderArgs) {
const { pathname } = new URL(request.url);
return servePublicPathFromStorage(pathname);
}
Superflare will determine the correct disk to use based on the publicPath
property of your storage
config. In this example, Superflare will use the default
disk to serve files from the MY_BUCKET
R2 bucket.
You can add any authentication or authorization mechanisms to this route to restrict access to your files.
Using a public R2 bucket
You can also serve public files from a R2 bucket's public domain. To do this, you can make the R2 bucket public by following Cloudflare's instructions.
Then, you can configure your storage
config to use the public domain:
// superflare.config.ts
import { defineConfig } from "superflare";
export default defineConfig((ctx) => {
return {
// ...
storage: {
default: {
binding: ctx.env.MY_BUCKET,
publicPath: "https://my-bucket.storage.example.com",
},
},
};
});
storage()
API
R2Input
The R2Input
type is a union of the following types:
type R2Input = string | ReadableStream | ArrayBuffer | ArrayBufferView | Blob;
storage().put(key: string, input: R2Input, options?: R2PutOptions)
Put a file in your R2 bucket.
storage().putRandom(input: R2Input, options?: R2PutOptions & { prefix?: string, extension?: string })
Put a file in your R2 bucket with a random name.
storage().get(key: string)
Get a file from your R2 bucket.
storage().delete(key: string)
Delete a file from your R2 bucket.
storage().url(key: string)
Get a URL to a file in your R2 bucket. Requires that the disk has a publicPath
property defined.