How to Build a Fastio API Integration With Remix Applications
Guide to fastio api integration with remix applications: Integrating Fastio with Remix means using server-side Action functions to parse multipart data and connect to the Fastio API. By streaming file uploads directly from your Remix server to Fastio's chunked upload endpoints, you bypass local memory limits and keep your application running fast.
Why Integrate Fastio API With Remix Applications?
If you build web applications that handle a lot of files, you know that how you manage uploads matters. If you pick the wrong method, your server slows down and users get frustrated.
Remix applications run on standard web protocols using the native Fetch API, Request, and Response objects. This edge-friendly design means you handle files differently than you would in older Node.js frameworks. Remix uses native web APIs for form data, which means you have to parse multipart data directly. But instead of forcing a file to download completely to your server's disk, Remix lets you intercept the incoming data stream and send it right where it needs to go.
Connecting the Fastio API to your Remix application lets you move heavy file processing off your servers. Fastio isn't just static storage; it's an intelligent workspace. Once files land in Fastio, the system automatically indexes them so they are searchable by meaning and available to AI agents through multiple Model Context Protocol tools. This setup keeps your Remix server lightweight while giving users better file management.
Helpful references: Fastio Workspaces, Fastio Collaboration, and Fastio AI.
Understanding Remix Multipart Form Data Parsing
Many web frameworks use middleware to process file uploads. These packages grab the request, save the entire file to your local disk, and then hand your application code a temporary file path. That creates a huge scaling problem. If ten users upload multiple video files at the exact same time, your server has to write multiple to its disk before your code even starts running.
Remix fixes this bottleneck by giving you direct access to the data stream. When someone submits a form with a file, Remix uses the unstable_parseMultipartFormData utility. This function doesn't just save the file for you. You have to provide a custom upload handler.
That upload handler is a callback function that receives the incoming file stream as an AsyncIterable<Uint8Array>. Since you control the handler, you decide where the stream goes. You can pipe it through a compression algorithm, send it to a cloud bucket, or push it to an external API like Fastio. Streaming the data like this uses almost zero memory on your Remix server, keeping performance high even when traffic spikes.
Designing the Fastio Chunked Upload Architecture
The Fastio API handles large files through a chunked upload sequence, which pairs well with Remix actions. Typical files can go in one request. Larger files use a short session so each POST from your action stays a manageable multipart body.
Start with POST https://api.fast.io/current/upload/ as multipart/form-data. Send name, size, action=create, instance_id (the workspace ID, a 19-digit numeric string), and folder_id (use root for the workspace root). Omit chunk on this first call. The response is {id}. Next, POST each piece to https://api.fast.io/current/upload/{id}/chunk/?order=N&size=N with multipart field chunk, and expect HTTP 202. When the pieces are done, POST https://api.fast.io/current/upload/{id}/complete/ (also 202). Then GET https://api.fast.io/current/upload/{id}/details/?wait=60 and read session.status and session.new_file_id.
Fastio writes the file into the workspace you passed as instance_id. Same-name upload into the same folder overwrites in place and keeps the old content as a recoverable version. The node_id stays stable, so a Remix loader can keep listing that file without updating stored IDs.
Step-by-Step: Fastio API Integration With Remix Applications
To build this upload system, you need to connect your frontend interface with your backend server logic. Generate an API key in Settings > Devices & Agents > API Keys, or with POST /current/user/auth/key/. Authenticated calls send Authorization: Bearer {api_key} to https://api.fast.io/current/. Keep the trailing slashes. Uploads are multipart/form-data. Here is how to parse multipart form data in a Remix action and list the workspace from a loader.
Step 1: Set Up the Frontend Form You need a standard HTML form that can send binary data. Use the native Remix Form component and make sure the encoding type is set correctly.
import { Form, useNavigation } from "@remix-run/react";
export default function FileUploadRoute() {
const navigation = useNavigation();
const isUploading = navigation.state === "submitting";
return (
<Form method="post" encType="multipart/form-data">
<input type="file" name="document" required />
<button type="submit" disabled={isUploading}>
{isUploading ? "Uploading..." : "Send to Fastio"}
</button>
</Form>
);
}
Step 2: Create the Remix Action Function When a user submits the form, your Remix route catches the request inside an Action function. That Action function intercepts the data and runs your custom Fastio logic.
import { ActionFunctionArgs, json } from "@remix-run/node";
import { unstable_parseMultipartFormData } from "@remix-run/node";
import { fastioUploadHandler } from "~/utils/fastio";
export async function action({ request }: ActionFunctionArgs) {
const formData = await unstable_parseMultipartFormData(
request,
fastioUploadHandler
);
const fileId = formData.get("document");
return json({ success: true, fileId });
}
Step 3: Implement the Custom Upload Handler
Your custom upload handler reads the Remix file stream so it can send size on the create call, then posts each piece to the chunk route.
import { unstable_composeUploadHandlers, unstable_createMemoryUploadHandler } from "@remix-run/node";
async function streamToFastio(data: AsyncIterable<Uint8Array>, filename: string) {
const pieces: Uint8Array[] = [];
let total = 0;
for await (const piece of data) {
pieces.push(piece);
total += piece.byteLength;
}
const auth = { Authorization: `Bearer ${process.env.FASTIO_API_KEY}` };
const start = new FormData();
start.append("name", filename);
start.append("size", String(total));
start.append("action", "create");
start.append("instance_id", process.env.FASTIO_WORKSPACE_ID);
start.append("folder_id", "root");
const sessionRes = await fetch("https://api.fast.io/current/upload/", {
method: "POST",
headers: auth,
body: start,
});
const { id } = await sessionRes.json();
for (let order = 0; order < pieces.length; order += 1) {
const piece = pieces[order];
const chunkForm = new FormData();
chunkForm.append("chunk", new Blob([piece]), filename);
await fetch(
`https://api.fast.io/current/upload/${id}/chunk/?order=${order}&size=${piece.byteLength}`,
{ method: "POST", headers: auth, body: chunkForm }
);
}
await fetch(`https://api.fast.io/current/upload/${id}/complete/`, {
method: "POST",
headers: auth,
});
const detailsRes = await fetch(
`https://api.fast.io/current/upload/${id}/details/?wait=60`,
{ headers: auth }
);
const details = await detailsRes.json();
return details.session.new_file_id;
}
export const fastioUploadHandler = unstable_composeUploadHandlers(
async ({ name, data, filename }) => {
if (name !== "document" || !filename) return undefined;
return await streamToFastio(data, filename);
},
unstable_createMemoryUploadHandler()
);
Small images and documents can skip the session and go in one call: POST https://api.fast.io/current/upload/ with name, size, chunk (the bytes), action=create, instance_id, and folder_id. HTTP 201 returns result, id, and new_file_id. Up to 200 files of 4MB or less can go through POST /current/upload/batch/.
Step 4: Load the Workspace From a Remix Loader
Action functions mutate storage. Loader functions read it. List a folder with GET /current/workspace/{workspace_id}/storage/{parent_id}/list/. Use root as parent_id for the workspace root. Query params are sort_by=name|updated|created|type (default name), sort_dir=asc|desc (default asc), page_size=100|250|500 (default 100), and cursor. Follow pagination.has_more and pagination.next_cursor. For one file, GET /current/workspace/{workspace_id}/storage/{node_id}/details/ returns metadata, and GET /current/workspace/{workspace_id}/storage/{node_id}/read/ returns the bytes.
import { json, type LoaderFunctionArgs } from "@remix-run/node";
export async function loader({ request }: LoaderFunctionArgs) {
const workspaceId = process.env.FASTIO_WORKSPACE_ID;
const url = new URL(request.url);
const cursor = url.searchParams.get("cursor");
const parentId = "root";
const listUrl = new URL(
`https://api.fast.io/current/workspace/${workspaceId}/storage/${parentId}/list/`
);
listUrl.searchParams.set("sort_by", "name");
listUrl.searchParams.set("sort_dir", "asc");
listUrl.searchParams.set("page_size", "100");
if (cursor) listUrl.searchParams.set("cursor", cursor);
const res = await fetch(listUrl, {
headers: { Authorization: `Bearer ${process.env.FASTIO_API_KEY}` },
});
const listing = await res.json();
return json({
listing,
hasMore: listing.pagination.has_more,
nextCursor: listing.pagination.next_cursor,
pageSize: listing.pagination.page_size,
});
}
Keep the API key on the server. The loader runs before the route renders, so the folder listing is ready when the page hydrates.
Bypassing Local Uploads With URL Imports
Streaming multipart form data works well, but the fast upload is the one you never actually process. People already store their files in cloud services like Google Drive or Dropbox. Forcing them to download a file just to re-upload it to your Remix app is a waste of time.
Fastio can import a remote file so your Remix action never touches the bytes. POST https://api.fast.io/current/web_upload/ with application/x-www-form-urlencoded fields source_url, file_name, profile_id, profile_type set to workspace or share, and folder_id. Fastio fetches the file from the source.
To set this up, change your frontend form to a text input for the URL. Your Remix action reads that string and posts it to /current/web_upload/. This saves your bandwidth and keeps the serverless function well under its time limit.
Give Your AI Agents Persistent Storage
Connect your Remix loaders and actions to Fastio workspaces, the REST API, and Ripley.
Connecting AI Agents to Uploaded Files
Uploading the file is just the start. Once your Remix app sends a file to Fastio, it lands in an intelligent workspace. You can connect these workspaces to tools like Storage for Agents to build features on top of your data.
Workspace intelligence indexes new files automatically. It extracts the text, creates summaries, and preps everything for semantic search. You don't have to build your own RAG pipeline from scratch because Ripley, the built-in RAG agent, can answer cited questions about those files.
Any file your users upload becomes queryable. If someone uploads a multiple-page legal contract through your Remix form, Ripley can answer questions about it. Start a chat with POST /current/workspace/{workspace_id}/ai/agent/ and send a message with POST /current/workspace/{workspace_id}/ai/agent/{chat_id}/message/. Agents can also connect at https://mcp.fast.io/mcp (or https://mcp.fast.io/mcp/key with a Bearer header) and use tools such as storage, find, ai (ask), and comment. Your Remix loader can watch the audit log with GET https://api.fast.io/current/events/search/ or long-poll GET https://api.fast.io/current/activity/poll/{entityId}?wait=95&lastactivity={timestamp}.
Evidence and Benchmarks for Streamed Uploads
Handling large files means thinking about your infrastructure limits. Fastio accepts a single-request upload for typical files and a chunked session for larger ones: POST https://api.fast.io/current/upload/ without chunk, then POST /current/upload/{id}/chunk/?order=N&size=N, POST /current/upload/{id}/complete/, and GET /current/upload/{id}/details/?wait=60 for session.status and session.new_file_id.
Moving file storage to Fastio means your Remix application can run on smaller server instances. You don't have to keep large block storage volumes next to the Node.js process, and the action only has to forward pieces instead of owning the archive.
Troubleshooting Fastio Loader Actions in Remix
Streaming uploads in Remix comes with a few common pitfalls. The most frequent issue is the "request body is already consumed" error. This happens if you try to read the request data more than once. The incoming request is a one-way stream. Once you pass it to unstable_parseMultipartFormData, you can't read it again anywhere else in your code.
Timeouts are another issue for large file transfers. If a user on a slow connection uploads a huge video file, the serverless function hosting your Remix app might hit its execution time limit. Bump up the timeout settings in your hosting platform, keep each POST small with the chunked session, or POST https://api.fast.io/current/web_upload/ (source_url, file_name, profile_id, profile_type, folder_id) so Fastio fetches the file and the bytes never pass through Remix.
Make sure your custom upload handler always has a fallback. Use the unstable_composeUploadHandlers function to combine your Fastio stream handler with a standard memory handler. If you don't do this, standard form fields like text inputs or checkboxes won't parse correctly in your Action function.
Frequently Asked Questions
How to upload files in Remix?
You upload files in Remix by creating a frontend form with the multipart form data encoding type. On the server side, you use an Action function combined with the `unstable_parseMultipartFormData` utility to catch and process the incoming file stream.
How to stream uploads to an external API in Remix?
Write a custom Remix upload handler that receives the file as an AsyncIterable. Read the pieces so you can send size on POST https://api.fast.io/current/upload/ (omit chunk to get an upload id), then POST each piece to /current/upload/{id}/chunk/?order=N&size=N with multipart field chunk, finish with POST /current/upload/{id}/complete/, and read GET /current/upload/{id}/details/?wait=60.
What is the maximum file size for Fastio?
Use the single-call POST https://api.fast.io/current/upload/ (fields name, size, chunk, action=create, instance_id, folder_id) for typical files. For larger payloads, POST the same form without chunk to get an upload id, POST each piece to /current/upload/{id}/chunk/?order=N&size=N, POST /current/upload/{id}/complete/, then GET /current/upload/{id}/details/?wait=60. Batch upload accepts up to 200 files, each 4MB or smaller.
Can I use Fastio with Remix loaders?
Yes. Action functions upload and mutate files. Loader functions list a folder with GET /current/workspace/{workspace_id}/storage/{parent_id}/list/, read metadata with GET /current/workspace/{workspace_id}/storage/{node_id}/details/, or stream bytes with GET /current/workspace/{workspace_id}/storage/{node_id}/read/. Follow pagination.has_more and pagination.next_cursor when the listing spans more than one page.
How does Fastio handle chunked uploads?
POST https://api.fast.io/current/upload/ without the chunk field to receive an upload id. Send each piece to POST /current/upload/{id}/chunk/?order=N&size=N (multipart field chunk) and expect HTTP 202. Finish with POST /current/upload/{id}/complete/ (also 202), then GET /current/upload/{id}/details/?wait=60 for session.status and session.new_file_id.
How can a Remix loader see new uploads?
Your Remix loader can read workspace activity with GET /current/events/search/ or long-poll GET /current/activity/poll/{entityId}?wait=95&lastactivity={timestamp}. When a new node_id appears, list the folder or start Ripley with POST /current/workspace/{workspace_id}/ai/agent/.
Related Resources
Give Your AI Agents Persistent Storage
Connect your Remix loaders and actions to Fastio workspaces, the REST API, and Ripley.