Grabrgrabr
Back to home

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/grabr

2. 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 dev

Open 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.