Llinuxctrl
Full Integration Guide

Using grabr with Vite + React

Because grabr performs aggressive parallel chunking and raw filesystem writes, it requires a Node.js or Bun backend environment. It cannot run directly inside a Vite React SPA.

In this tutorial, we will build a full-stack application. We will create an Express.js API backend to run grabr, and a Vite React frontend that tells the backend what files to download.

1. Project Setup

mkdir my-grabr-app
cd my-grabr-app

npm create vite@latest frontend -- --template react-ts
cd frontend
npm install
cd ..

mkdir backend
cd backend
npm init -y
npm install express cors @linuxctrl/grabr
npm install -D typescript @types/express @types/cors ts-node

2. Build the Express Backend

Create backend/server.ts:

import express from "express";
import cors from "cors";
import path from "path";
import fs from "fs";
import { Downloader } from "@linuxctrl/grabr";

const app = express();
app.use(cors());
app.use(express.json());

const downloader = new Downloader();
downloader.start();

app.post("/api/download", async (req, res) => {
  const { url, filename } = req.body;
  const outputDir = path.join(process.cwd(), "downloads");
  if (!fs.existsSync(outputDir)) {
    fs.mkdirSync(outputDir, { recursive: true });
  }

  try {
    const job = await downloader.addJob(url, {
      outputDir,
      filename,
      chunks: 8,
    });
    console.log(`Job added: ${job.filename}`);
    res.json({ success: true, jobId: job.id });
  } catch (error) {
    console.error("Error starting grabr:", error);
    res.status(500).json({ success: false, error: String(error) });
  }
});

const PORT = 3001;
app.listen(PORT, () => {
  console.log(`Backend running on http://localhost:${PORT}`);
});

3. Build the Vite React Frontend

Open frontend/src/App.tsx and replace it with:

import { useState } from 'react'

function App() {
  const [url, setUrl] = useState("https://speed.hetzner.de/100MB.bin");
  const [status, setStatus] = useState("");

  const handleDownload = async () => {
    setStatus("Connecting to backend...");
    try {
      const response = await fetch("http://localhost:3001/api/download", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ url, filename: "test-file.bin" })
      });
      const data = await response.json();
      if (data.success) {
        setStatus(`Downloading! Check the backend terminal and downloads folder.`);
      } else {
        setStatus(`Error: ${data.error}`);
      }
    } catch (err) {
      setStatus("Failed to connect to backend");
    }
  };

  return (
    <main style={{ padding: "40px", fontFamily: "sans-serif" }}>
      <h1>Grabr Vite Demo</h1>
      <div style={{ display: "flex", gap: "10px", marginTop: "20px" }}>
        <input
          type="text"
          value={url}
          onChange={(e) => setUrl(e.target.value)}
          style={{ width: "300px", padding: "8px" }}
        />
        <button onClick={handleDownload} style={{ padding: "8px 16px", cursor: "pointer" }}>
          Download via Backend
        </button>
      </div>
      {status && <p style={{ marginTop: "20px", fontWeight: "bold" }}>{status}</p>}
    </main>
  )
}

export default App

4. Run the Full Stack

You need to start both servers. Open two terminal windows.

Terminal 1 (Backend):

cd backend
npx ts-node server.ts

Terminal 2 (Frontend):

cd frontend
npm run dev

Open the Vite frontend URL in your browser, click Download via Backend, and watch your Express terminal!

Advanced: Live Progress via WebSocket

Want to show real-time progress bars in React? Use a WebSocket library like socket.io to listen to grabr's job:progress events in Express, and emit them to your Vite frontend!