File Sharing Open source demo · Next.js + Filestack

Drop a File. Get a Shareable Link.

A 500 KB instant file-sharing demo. Drag in any file, get a /s/<code> URL you can copy and send. Backed by Filestack’s store API and a Turso SQLite database.

Next.js 16
Turso / libSQL
Filestack REST API
Trusted by
SendGrid logo with stylized gray text and overlapping square shapes on the left.
LinkedIn logo followed by the word SlideShare in gray text on a light background.
The word teachable is written in all lowercase, sans-serif letters with a colon between teach and able, in a light purple color on a light background.
A gray Airtable logo featuring a geometric cube design to the left of the word Airtable in bold, modern font.
Zenefits logo featuring a stylized hummingbird icon to the left of the word “ZENEFITS” in uppercase, sans-serif letters.
The stack

Four moving parts. That’s it.

Filestack handles the file plane. The app is a thin Next.js UI on top, with a single SQLite table tracking shares.

Frontend
Next.js 16
app router · React 19
File API
Filestack REST
POST /api/store/S3
Database
Turso / libSQL
1 table · shares · Drizzle ORM
Styling
Tailwind CSS 4
+ nanoid for share codes
How it works

Four Steps From Drop to Shareable Link

Every share follows the same path: drop, upload, save, share. The browser talks to Filestack directly, and the Next.js API just mints the code.

01 / DROP

User picks one file

The dropzone catches a drag event or opens the native file picker. One file at a time, capped client-side at 500 KB.

02 / UPLOAD

Direct POST to Filestack

An XHR sends the file straight to filestackapi.com/api/store/S3 with the public API key. Progress is streamed back into the UI.

03 / SAVE

Mint a 10-char code

The browser POSTs the Filestack handle, URL, and a device fingerprint to /api/share. The server rate-limits per fingerprint and inserts a row keyed by nanoid(10).

04 / SHARE

Copy the /s/<code> URL

The success card surfaces a one-click copy button. Visiting /s/<code> renders a preview page powered by Filestack transformations.

The upload

One XHR to Filestack, one POST to your API

No SDK. The Uploader posts the raw file straight to Filestack’s store endpoint, watches progress, then sends the returned handle to /api/share to mint a code.

  • Drag-and-drop or native file picker — one file at a time
  • 500 KB cap enforced client-side via MAX_FILE_BYTES
  • Live upload progress streamed from xhr.upload.onprogress
  • Device fingerprint sent with the save call for rate limiting
components/Uploader.tsx
function uploadToFilestack(file, apiKey, onProgress) {
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest()
    const params = new URLSearchParams({ key: apiKey, filename: file.name })
    xhr.open('POST',
      `https://www.filestackapi.com/api/store/S3?${params}`)
    xhr.setRequestHeader('Content-Type',
      file.type || 'application/octet-stream')
    xhr.upload.onprogress = (e) => {
      if (e.lengthComputable)
        onProgress(Math.round((e.loaded / e.total) * 100))
    }
    xhr.onload = () => {
      if (xhr.status >= 200 && xhr.status < 300)
        resolve(JSON.parse(xhr.responseText))
        else reject(new Error('Upload failed'))
    }
    xhr.send(file)
  })
}
app/api/share/route.ts
import { NextResponse } from 'next/server'
import { nanoid } from 'nanoid'
import { db, schema } from '@/lib/db'
export async function POST(req: Request) {
  const b = await req.json()
  // only accept Filestack CDN URLs
  if (!/^https:\/\/cdn\.filestackcontent\.com\//.test(b.url))
    return NextResponse.json({ error: 'Bad URL' }, { status: 400 })
  // rate limit: 10 uploads / 24h per fingerprint
  const since = Date.now() - 24 * 60 * 60 * 1000
  const [{ count: n }] = await db.select({ count: count() })
    .from(schema.shares)
    .where(and(
      eq(schema.shares.fingerprint, b.fingerprint),
      gt(schema.shares.createdAt, since)))
  if (n >= 10) return NextResponse.json({ error: 'Rate limit' }, { status: 429 })
  const code = nanoid(10)
  await db.insert(schema.shares).values({
    id: nanoid(), code, ...b, createdAt: Date.now()
  })
  return NextResponse.json({ code })
}
The link

Validation, rate limiting, and a 10-char code

The save route doesn’t sign anything — it leans on Filestack’s CDN being the source of truth. Instead it validates the URL shape, rate-limits per device, and stores the handle under a short nanoid code.

  • 10-character nanoid share codes
  • URL regex check: only cdn.filestackcontent.com links accepted
  • Per-fingerprint rate limit: 10 uploads / 24 hours
  • Single shares table, 2 indexes (code, fingerprint)
The repo

A small Next.js app you can read in one sitting.

Fireshare lives at apps/fs-filesharing in the public use-cases monorepo. The upload, the API route, and the schema are the three files worth reading first. Fork it, drop in your Filestack key, and run it locally.

apps/fs-filesharing/ ├── app/ │ ├── layout.tsx // root layout │ ├── page.tsx // landing + uploader │ ├── s/[code]/ │ │ ├── page.tsx // share view + transforms │ │ └── not-found.tsx // 404 for bad codes │ └── api/share/ │ └── route.ts // validate, rate-limit, insert ├── components/ │ ├── Uploader.tsx // XHR + share state machine │ ├── Transformations.tsx // Filestack image transforms │ ├── CopyButton.tsx // clipboard helper │ ├── Navbar.tsx │ └── Logo.tsx ├── lib/ │ ├── db/ │ │ ├── index.ts // Drizzle + libSQL client │ │ └── schema.ts // shares table │ └── utils.ts // MAX_FILE_BYTES, formatBytes ├── drizzle.config.ts ├── .env.example // NEXT_PUBLIC_FILESTACK_API_KEY,// TURSO_DATABASE_URL, TURSO_AUTH_TOKEN └── README.md ← start here

Build Your Own File Sharing App, Free

FAQ

How Fireshare Works Under the Hood

What database does the demo use?

Turso (libSQL / SQLite) via Drizzle ORM. There’s a single shares table with indexes on code and fingerprint. Swap lib/db/index.ts for any Drizzle adapter — Postgres, MySQL, or a local SQLite file — and the rest of the app keeps working.

How are uploads kept secure?

The demo doesn’t sign Filestack policies. The browser uploads directly with the public API key, and /api/share validates the returned URL against a regex that requires cdn.filestackcontent.com. For production sharing you’d want to layer signed policies on top — the docs cover the pattern.

What's the maximum file size?

500 KB. The cap is enforced both client-side in the Uploader and server-side in the share route via MAX_FILE_BYTES in lib/utils.ts. Filestack itself supports much larger files — bump the constant and the limit moves with you.

Does it support file previews?

Yes — the /s/[code] page renders a Transformations component that uses Filestack’s image transformations to show inline previews.

Is there rate limiting?

Yes — 10 shares per 24 hours per device. The browser sends a base64-encoded fingerprint (user agent + locale + screen + timezone) on each save, and the API counts recent rows for that fingerprint before inserting.