Skip to content

Basic Usage

This guide demonstrates how to set up a basic Uploadista server and client for handling file uploads with flow processing.

  1. Install Dependencies

    Install the core server package, your chosen adapter, and plugins:

    npm install @uploadista/server @uploadista/adapters-hono hono

    Install storage packages based on your needs:

    File storage:

    npm install @uploadista/data-store-s3
    npm install @uploadista/data-store-filesystem

    Metadata storage:

    npm install @uploadista/kv-store-redis
    npm install @uploadista/kv-store-filesystem

    Plugins (optional):

    npm install @uploadista/flow-images-sharp
  2. Define Your Flows

    Create flows using declarative DAG definitions:

    flows.ts
    import { createFlow, createInputNode } from "@uploadista/core";
    import {
    createOptimizeNode,
    createResizeNode,
    } from "@uploadista/flow-images-nodes";
    // Simple flow: input only (direct upload, no processing)
    // The input node is a sink (no outgoing edges), so the file
    // is automatically persisted to target storage.
    export const simpleFlow = createFlow({
    flowId: "simple",
    name: "Simple Upload",
    nodes: {
    input: createInputNode("input"),
    },
    edges: [],
    });
    // Image optimization flow: input → optimize (sink)
    // The optimize node is a sink, so the optimized file
    // is automatically persisted to target storage.
    export const optimizeFlow = createFlow({
    flowId: "optimize-image",
    name: "Optimize Image",
    nodes: {
    input: createInputNode("input"),
    optimize: createOptimizeNode("optimize", {
    format: "webp",
    quality: 80,
    }),
    },
    edges: [{ source: "input", target: "optimize" }],
    });
    // Image resize flow: input → resize → optimize (sink)
    export const resizeFlow = createFlow({
    flowId: "resize-image",
    name: "Resize Image",
    nodes: {
    input: createInputNode("input"),
    resize: createResizeNode("resize", {
    width: 800,
    height: 600,
    fit: "cover",
    }),
    optimize: createOptimizeNode("optimize", { quality: 85 }),
    },
    edges: [
    { source: "input", target: "resize" },
    { source: "resize", target: "optimize" },
    ],
    });
    // Export all flows
    export const flows = {
    simple: simpleFlow,
    "optimize-image": optimizeFlow,
    "resize-image": resizeFlow,
    };
  3. Create Uploadista Server

    Create the server with stores, flows, plugins, and WebSocket support:

    import http from "node:http";
    import express from "express";
    import { WebSocketServer } from "ws";
    import { createUploadistaServer } from "@uploadista/server";
    import { expressAdapter } from "@uploadista/adapters-express";
    import { fileStore } from "@uploadista/data-store-filesystem";
    import { fileKvStore } from "@uploadista/kv-store-filesystem";
    import { imagePlugin } from "@uploadista/flow-images-sharp";
    import { flows } from "./flows";
    const app = express();
    const server = http.createServer(app);
    // Setup stores
    const kvStore = fileKvStore({ directory: "./uploads" });
    const dataStore = fileStore({
    directory: "./uploads",
    deliveryUrl: "http://localhost:3000",
    });
    // Create Uploadista server
    const uploadistaServer = await createUploadistaServer({
    dataStore,
    kvStore,
    flows,
    plugins: [imagePlugin()],
    adapter: expressAdapter(),
    });
    // HTTP routes
    app.all("/uploadista/api/*splat", (request, response, next) => {
    uploadistaServer.handler({ request, response, next });
    });
    // WebSocket server for real-time progress
    const wss = new WebSocketServer({ server });
    wss.on("connection", (ws, req) => {
    uploadistaServer.websocketHandler(ws, req);
    });
    server.listen(3000, () => {
    console.log("Server running on http://localhost:3000");
    });

Install the React client package:

npm install @uploadista/react

Set up the provider in your app:

import { UploadistaProvider } from "@uploadista/react";
function App() {
return (
<UploadistaProvider
baseUrl="http://localhost:3000"
uploadistaBasePath="uploadista"
storageId="local"
chunkSize={1024 * 1024} // 1MB chunks
storeFingerprintForResuming={true}
>
{/* Your app content */}
</UploadistaProvider>
);
}

The Upload compound component provides a declarative way to build upload UIs:

import { Upload } from "@uploadista/react";
function FileUploader() {
return (
<Upload
multiple
maxConcurrent={3}
autoStart={false}
onComplete={(results) => console.log("All uploads complete:", results)}
>
{/* Drag & Drop Zone */}
<Upload.DropZone accept="image/*" maxFiles={10}>
{({ isDragging, getRootProps, openFilePicker }) => (
<div
{...getRootProps()}
style={{
border: `2px dashed ${isDragging ? "blue" : "gray"}`,
padding: "2rem",
textAlign: "center",
cursor: "pointer",
}}
onClick={openFilePicker}
>
{isDragging ? "Drop files here..." : "Click or drag files to upload"}
</div>
)}
</Upload.DropZone>
{/* Upload Items List */}
<Upload.Items>
{({ items }) =>
items.map((item) => (
<Upload.Item key={item.id} id={item.id}>
{({ file, state, abort, retry, remove }) => (
<div style={{ display: "flex", alignItems: "center", gap: "1rem" }}>
<span>{file.name}</span>
<progress value={state.progress} max={100} />
<span>{state.progress}%</span>
<span>{state.status}</span>
{state.status === "uploading" && (
<button onClick={abort}>Cancel</button>
)}
{state.status === "error" && (
<button onClick={retry}>Retry</button>
)}
<button onClick={remove}>Remove</button>
</div>
)}
</Upload.Item>
))
}
</Upload.Items>
{/* Global Progress */}
<Upload.Progress>
{({ progress, status }) => (
<div>
Overall: {progress}% - {status}
</div>
)}
</Upload.Progress>
</Upload>
);
}

The Flow compound component executes server-defined processing flows:

import { Flow } from "@uploadista/react";
function ImageProcessor() {
const [outputs, setOutputs] = useState([]);
return (
<Flow
flowId="optimize-image"
storageId="local"
onSuccess={(results) => setOutputs(results)}
onError={(error) => console.error("Flow failed:", error)}
>
{/* Flow Inputs - automatically maps to flow input nodes */}
<Flow.Inputs>
{({ inputs, isLoading }) =>
isLoading ? (
<p>Loading flow...</p>
) : (
inputs.map((input) => (
<Flow.Input nodeId={input.nodeId} key={input.nodeId}>
<div>
<h4>{input.name}</h4>
<Flow.Input.DropZone accept="image/*">
{({ isDragging, getRootProps, openFilePicker }) => (
<div
{...getRootProps()}
onClick={openFilePicker}
style={{
border: `2px dashed ${isDragging ? "blue" : "gray"}`,
padding: "1rem",
cursor: "pointer",
}}
>
Drop image here
</div>
)}
</Flow.Input.DropZone>
<Flow.Input.Preview>
{({ value }) =>
value && (
<img
src={URL.createObjectURL(value)}
alt="Preview"
style={{ maxWidth: 200 }}
/>
)
}
</Flow.Input.Preview>
</div>
</Flow.Input>
))
)
}
</Flow.Inputs>
{/* Submit Button */}
<Flow.Submit>Process Image</Flow.Submit>
{/* Progress */}
<Flow.Progress>
{({ progress, status }) => (
<div>
{status === "processing" && <progress value={progress} max={100} />}
</div>
)}
</Flow.Progress>
{/* Status */}
<Flow.Status>
{({ status }) => <p>Status: {status}</p>}
</Flow.Status>
{/* Error Display */}
<Flow.Error>
{({ error }) => error && <p style={{ color: "red" }}>{error.message}</p>}
</Flow.Error>
{/* Outputs */}
{outputs.length > 0 && (
<div>
<h4>Processed Files:</h4>
{outputs.map((output, i) => (
<a key={i} href={output.url} target="_blank" rel="noreferrer">
{output.filename}
</a>
))}
</div>
)}
</Flow>
);
}

For more control, use the hooks directly:

import { useUpload, useMultiUpload, useDragDrop } from "@uploadista/react";
// Single file upload
function SingleUpload() {
const upload = useUpload({
onSuccess: (result) => console.log("Uploaded:", result),
onError: (error) => console.error("Failed:", error),
onProgress: (progress) => console.log(`${progress}%`),
});
return (
<div>
<input
type="file"
onChange={(e) => {
const file = e.target.files?.[0];
if (file) upload.upload(file);
}}
/>
{upload.state.status === "uploading" && (
<div>
<progress value={upload.state.progress} max={100} />
<button onClick={upload.abort}>Cancel</button>
</div>
)}
</div>
);
}
// Multiple file upload with concurrency control
function MultiUpload() {
const multiUpload = useMultiUpload({
maxConcurrent: 3,
onUploadSuccess: (id, result) => console.log(`${id} uploaded`),
onComplete: (results) => console.log("All done:", results),
});
return (
<div>
<input
type="file"
multiple
onChange={(e) => multiUpload.addFiles(Array.from(e.target.files || []))}
/>
<button onClick={multiUpload.startAll}>Upload All</button>
<button onClick={multiUpload.abortAll}>Cancel All</button>
{multiUpload.items.map((item) => (
<div key={item.id}>
{item.file.name}: {item.state.progress}%
</div>
))}
</div>
);
}
// Drag & drop with validation
function DragDropUpload() {
const dragDrop = useDragDrop({
accept: ["image/*", "video/*"],
maxFiles: 10,
maxFileSize: 50 * 1024 * 1024, // 50MB
onFilesReceived: (files) => console.log("Files:", files),
onValidationError: (errors) => console.error("Invalid:", errors),
});
return (
<div {...dragDrop.dragHandlers}>
<input {...dragDrop.inputProps} />
{dragDrop.state.isDragging ? "Drop here" : "Drag files"}
</div>
);
}

Add authentication headers to the provider:

<UploadistaProvider
baseUrl="http://localhost:3000"
uploadistaBasePath="uploadista"
storageId="local"
headers={{
Authorization: `Bearer ${authToken}`,
}}
>
{/* Your app */}
</UploadistaProvider>

Configure authentication on the server via the adapter:

const uploadistaServer = await createUploadistaServer({
dataStore,
kvStore,
flows,
adapter: honoAdapter({
authMiddleware: async (c) => {
const token = c.req.header("Authorization")?.replace("Bearer ", "");
if (!token) return null;
const user = await verifyToken(token);
return {
clientId: user.id,
permissions: user.permissions,
};
},
}),
});
const uploadistaServer = await createUploadistaServer({
// Required
dataStore, // File storage (S3, filesystem, etc.)
kvStore, // Metadata storage (Redis, filesystem, etc.)
flows, // Flow definitions object
adapter, // Framework adapter (Express, Fastify, Hono)
// Optional
plugins: [ // Processing plugins
imagePlugin(),
videoPlugin(),
zipPlugin(),
],
eventBroadcaster, // For multi-instance deployments (Redis)
withTracing: true, // OpenTelemetry integration
});
<UploadistaProvider
baseUrl="http://localhost:3000" // Server URL (required)
uploadistaBasePath="uploadista" // API path prefix (default: "uploadista")
storageId="local" // Storage identifier (required)
chunkSize={1024 * 1024} // Chunk size in bytes (default: 1MB)
storeFingerprintForResuming={true} // Enable resumable uploads
headers={{ // Custom headers (optional)
Authorization: `Bearer ${token}`,
}}
>
{children}
</UploadistaProvider>

Now that you understand the basics, explore more advanced features:

Image Upload with Preview using Upload Component

Section titled “Image Upload with Preview using Upload Component”
import { Upload } from "@uploadista/react";
import { useState } from "react";
function ImageUploadWithPreview() {
return (
<Upload onComplete={(results) => console.log("Uploaded:", results)}>
<Upload.DropZone accept="image/*" maxFiles={1}>
{({ isDragging, getRootProps, openFilePicker }) => (
<div
{...getRootProps()}
onClick={openFilePicker}
style={{
border: `2px dashed ${isDragging ? "blue" : "gray"}`,
padding: "2rem",
textAlign: "center",
cursor: "pointer",
}}
>
{isDragging ? "Drop image here..." : "Click or drag an image"}
</div>
)}
</Upload.DropZone>
<Upload.Items>
{({ items }) =>
items.map((item) => (
<Upload.Item key={item.id} id={item.id}>
{({ file, state, remove }) => (
<div>
<img
src={URL.createObjectURL(file)}
alt="Preview"
style={{ maxWidth: 200 }}
/>
<p>{file.name}</p>
<progress value={state.progress} max={100} />
<button onClick={remove}>Remove</button>
</div>
)}
</Upload.Item>
))
}
</Upload.Items>
</Upload>
);
}
import { Flow } from "@uploadista/react";
function ImageOptimizer() {
return (
<Flow flowId="optimize-image" storageId="local">
<Flow.Inputs>
{({ inputs }) =>
inputs.map((input) => (
<Flow.Input nodeId={input.nodeId} key={input.nodeId}>
<Flow.Input.DropZone accept="image/*">
{({ getRootProps, openFilePicker }) => (
<div {...getRootProps()} onClick={openFilePicker}>
Select image to optimize
</div>
)}
</Flow.Input.DropZone>
<Flow.Input.Preview>
{({ value }) =>
value && (
<img
src={URL.createObjectURL(value)}
alt="Input"
style={{ maxWidth: 200 }}
/>
)
}
</Flow.Input.Preview>
</Flow.Input>
))
}
</Flow.Inputs>
<Flow.Submit>Optimize Image</Flow.Submit>
<Flow.Progress>
{({ progress, status }) =>
status === "processing" && <progress value={progress} max={100} />
}
</Flow.Progress>
<Flow.Error>
{({ error }) => error && <p style={{ color: "red" }}>{error.message}</p>}
</Flow.Error>
</Flow>
);
}

Check that:

  • Your server is running and accessible
  • The baseUrl in the client matches your server URL
  • The uploadistaBasePath matches your server route configuration
  • CORS is properly configured on your server
  • Authentication tokens are valid (if using auth)

Ensure:

  • WebSocket server is properly configured and accessible
  • WebSocket upgrade handler is set up on the server
  • Network/firewall allows WebSocket connections
  • Check browser console for WebSocket connection errors

Verify:

  • The flowId matches a defined flow on the server
  • Required plugins are installed and configured
  • All flow input nodes have received files
  • Check server logs for detailed error messages

Verify:

  • storeFingerprintForResuming is enabled on the client
  • The KV store is properly configured and accessible
  • Upload session hasn’t expired
  • Same storageId is being used

For more help, open an issue on GitHub.