Using grabr with TanStack Start
TanStack Start is a modern, full-stack React framework powered by TanStack Router. Since grabrrequires Node.js to perform aggressive file I/O and parallel chunking, we can perfectly utilize TanStack Start's createServerFn to run grabr securely on the server while triggering it from the client.
1. Create a TanStack Start Project
bash
bun create tsrouter-app my-grabr-app
cd my-grabr-app
bun add @linuxctrl/grabr2. Build the Server Function
Create a file at app/server/download.ts:
typescript
import { createServerFn } from '@tanstack/start'
import { Downloader } from '@linuxctrl/grabr'
import path from 'path'
import fs from 'fs'
export const triggerDownload = createServerFn({ method: 'POST' })
.validator((data: { url: string; filename: string }) => data)
.handler(async ({ data }) => {
const { url, filename } = data
const outputDir = path.join(process.cwd(), 'downloads')
if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir, { recursive: true })
}
const downloader = new Downloader()
await downloader.start()
// Track progress server-side
downloader.on('job:progress', ({ jobId, speed, eta, downloadedBytes, totalBytes }) => {
const pct = totalBytes > 0 ? Math.round(downloadedBytes / totalBytes * 100) : 0
console.log(`${jobId}: ${pct}% at ${Math.round(speed / 1e6)} MB/s, ETA: ${eta}s`)
})
downloader.on('job:status', ({ jobId, status }) => {
console.log(`${jobId}: ${status}`)
})
try {
const job = await downloader.addJob(url, {
outputDir,
filename,
chunks: 8,
})
console.log(`Started downloading ${job.filename} (ID: ${job.id})`)
return { success: true, jobId: job.id }
} catch (error) {
console.error("Download failed to start:", error)
return { success: false, error: String(error) }
}
})3. Create the Frontend UI
Open app/routes/index.tsx and replace its contents:
typescript
import { createFileRoute } from '@tanstack/react-router'
import { triggerDownload } from '../server/download'
import { useState } from 'react'
export const Route = createFileRoute('/')({
component: Home,
})
function Home() {
const [url, setUrl] = useState('https://speed.hetzner.de/100MB.bin')
const [status, setStatus] = useState('')
const handleDownload = async () => {
setStatus('Starting grabr engine...')
const result = await triggerDownload({ data: { url, filename: 'test-file.bin' } })
if (result.success) {
setStatus('Downloading! Check your terminal and the ./downloads folder.')
} else {
setStatus(`Error: ${result.error}`)
}
}
return (
<main className="p-10 max-w-xl mx-auto space-y-4 font-sans">
<h1 className="text-2xl font-bold">Grabr + TanStack Start Demo</h1>
<input
type="text"
value={url}
onChange={(e) => setUrl(e.target.value)}
className="w-full p-2 border rounded text-black"
placeholder="Enter file URL..."
/>
<button
onClick={handleDownload}
className="px-4 py-2 bg-cyan-500 text-white font-bold rounded hover:bg-cyan-600 transition-colors"
>
Download on Server
</button>
{status && <p className="text-sm font-medium mt-4">{status}</p>}
</main>
)
}4. Run the Application
bash
bun run devOpen your browser, click Download on Server, and watch your terminal. The TanStack Start backend will instantly begin using grabr to parallel-chunk your download at maximum speed into the ./downloads folder.
Pro Tip: In production, run
grabr daemon start as a background service and connect to its REST API at /api/jobs instead of creating a Downloader per request. The daemon also broadcasts real-time progress via WebSocket at /ws.