Basic Usage
This guide demonstrates how to set up a basic Uploadista server and client for handling file uploads with flow processing.
Server Setup
Section titled “Server Setup”-
Install Dependencies
Install the core server package, your chosen adapter, and plugins:
npm install @uploadista/server @uploadista/adapters-hono honopnpm add @uploadista/server @uploadista/adapters-hono honoyarn add @uploadista/server @uploadista/adapters-hono hononpm install @uploadista/server @uploadista/adapters-express expresspnpm add @uploadista/server @uploadista/adapters-express expressyarn add @uploadista/server @uploadista/adapters-express expressInstall storage packages based on your needs:
File storage:
npm install @uploadista/data-store-s3npm install @uploadista/data-store-filesystempnpm add @uploadista/data-store-s3pnpm add @uploadista/data-store-filesystemyarn add @uploadista/data-store-s3yarn add @uploadista/data-store-filesystemMetadata storage:
npm install @uploadista/kv-store-redisnpm install @uploadista/kv-store-filesystempnpm add @uploadista/kv-store-redispnpm add @uploadista/kv-store-filesystemyarn add @uploadista/kv-store-redisyarn add @uploadista/kv-store-filesystemPlugins (optional):
npm install @uploadista/flow-images-sharppnpm add @uploadista/flow-images-sharpyarn add @uploadista/flow-images-sharp -
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 flowsexport const flows = {simple: simpleFlow,"optimize-image": optimizeFlow,"resize-image": resizeFlow,}; -
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 storesconst kvStore = fileKvStore({ directory: "./uploads" });const dataStore = fileStore({directory: "./uploads",deliveryUrl: "http://localhost:3000",});// Create Uploadista serverconst uploadistaServer = await createUploadistaServer({dataStore,kvStore,flows,plugins: [imagePlugin()],adapter: expressAdapter(),});// HTTP routesapp.all("/uploadista/api/*splat", (request, response, next) => {uploadistaServer.handler({ request, response, next });});// WebSocket server for real-time progressconst wss = new WebSocketServer({ server });wss.on("connection", (ws, req) => {uploadistaServer.websocketHandler(ws, req);});server.listen(3000, () => {console.log("Server running on http://localhost:3000");});import Fastify from "fastify";import websocket from "@fastify/websocket";import { createUploadistaServer } from "@uploadista/server";import { fastifyAdapter } from "@uploadista/adapters-fastify";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 fastify = Fastify({ logger: true });await fastify.register(websocket);// Setup storesconst kvStore = fileKvStore({ directory: "./uploads" });const dataStore = fileStore({directory: "./uploads",deliveryUrl: "http://localhost:3000",});// Create Uploadista serverconst uploadistaServer = await createUploadistaServer({dataStore,kvStore,flows,plugins: [imagePlugin()],adapter: fastifyAdapter(),});// Binary upload supportfastify.addContentTypeParser("application/octet-stream",{ parseAs: "buffer" },(req, body, done) => done(null, body));// HTTP routesfastify.all("/uploadista/api/*", async (request, reply) => {return uploadistaServer.handler({ request, reply });});// WebSocket routes for real-time progressfastify.get("/uploadista/ws/upload/:uploadId",{ websocket: true },(socket, req) => uploadistaServer.websocketHandler(socket, req.raw));fastify.get("/uploadista/ws/flow/:jobId",{ websocket: true },(socket, req) => uploadistaServer.websocketHandler(socket, req.raw));await fastify.listen({ port: 3000 });import { serve } from "@hono/node-server";import { Hono } from "hono";import { createUploadistaServer } from "@uploadista/server";import { honoAdapter } from "@uploadista/adapters-hono";import { s3Store } from "@uploadista/data-store-s3";import { redisKvStore } from "@uploadista/kv-store-redis";import { imagePlugin } from "@uploadista/flow-images-sharp";import { createClient } from "redis";import { flows } from "./flows";const app = new Hono();// Setup Redisconst redisClient = createClient({ url: process.env.REDIS_URL });await redisClient.connect();// Setup storesconst kvStore = redisKvStore({ redis: redisClient });const dataStore = s3Store({deliveryUrl: process.env.CDN_URL!,s3ClientConfig: {bucket: process.env.S3_BUCKET!,region: process.env.AWS_REGION!,credentials: {accessKeyId: process.env.AWS_ACCESS_KEY_ID!,secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,},},});// Create Uploadista serverconst uploadistaServer = await createUploadistaServer({dataStore,kvStore,flows,plugins: [imagePlugin()],adapter: honoAdapter(),});// HTTP routesapp.all("/uploadista/api/*", (c) => uploadistaServer.handler(c));// Start server with WebSocket supportconst server = serve({ fetch: app.fetch, port: 3000 }, (info) => {console.log(`Server running on http://localhost:${info.port}`);});server.on("upgrade", (request, socket, head) => {uploadistaServer.handleUpgrade(request, socket, head);});
Client Setup
Section titled “Client Setup”React Client
Section titled “React Client”Install the React client package:
npm install @uploadista/reactpnpm add @uploadista/reactyarn add @uploadista/reactSet 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> );}Using the Upload Component
Section titled “Using the Upload Component”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> );}Using the Flow Component
Section titled “Using the Flow Component”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> );}Using Hooks
Section titled “Using Hooks”For more control, use the hooks directly:
import { useUpload, useMultiUpload, useDragDrop } from "@uploadista/react";
// Single file uploadfunction 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 controlfunction 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 validationfunction 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> );}Authentication
Section titled “Authentication”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, }; }, }),});Configuration Options
Section titled “Configuration Options”Server Configuration
Section titled “Server Configuration”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});Client Configuration
Section titled “Client Configuration”<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>Next Steps
Section titled “Next Steps”Now that you understand the basics, explore more advanced features:
- Flows Engine - How flow processing pipelines work
- Flow Nodes - Available nodes and architecture patterns
- Authentication - Secure your uploads
- Data Stores - Configure different storage backends
- Plugins - Add image, video, and document processing
Common Use Cases
Section titled “Common Use Cases”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> );}Image Processing with Flow
Section titled “Image Processing with Flow”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> );}Troubleshooting
Section titled “Troubleshooting”Upload Fails Immediately
Section titled “Upload Fails Immediately”Check that:
- Your server is running and accessible
- The
baseUrlin the client matches your server URL - The
uploadistaBasePathmatches your server route configuration - CORS is properly configured on your server
- Authentication tokens are valid (if using auth)
Progress Not Updating in Real-Time
Section titled “Progress Not Updating in Real-Time”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
Flow Processing Fails
Section titled “Flow Processing Fails”Verify:
- The
flowIdmatches 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
Uploads Not Resuming
Section titled “Uploads Not Resuming”Verify:
storeFingerprintForResumingis enabled on the client- The KV store is properly configured and accessible
- Upload session hasn’t expired
- Same
storageIdis being used
For more help, open an issue on GitHub.