Use Cases (अपडेट: 1/6/2026)

Claude Code से सुरक्षित File Upload बनाना: FormData, Validation, Preview और S3

SaaS के लिए सुरक्षित file upload गाइड: Claude Code, File API, FormData, fetch, server validation, progress और S3 निर्णय।

Claude Code से सुरक्षित File Upload बनाना: FormData, Validation, Preview और S3

File upload छोटा feature लगता है, लेकिन SaaS में यह जल्दी ही security boundary बन जाता है। Profile image, invoice PDF, CSV import, contract, chat attachment - सबकी शुरुआत एक ही button से होती है: file चुनिए। लेकिन उसके पीछे browser API, request format, server validation, storage permission, public/private access, preview, progress, cost और audit log जैसे फैसले होते हैं।

Claude Code इस काम में बहुत मदद कर सकता है। वह React component, API route, validation helper, storage adapter और README बना सकता है। लेकिन prompt साफ होना चाहिए। अगर आप सिर्फ “file upload बना दो” लिखते हैं, तो code demo में चल सकता है, पर original filename save कर सकता है, browser MIME type पर ज्यादा भरोसा कर सकता है, size limit भूल सकता है, या upload के बाद public URL लौटा सकता है।

इस guide में हम review-friendly order अपनाते हैं। पहले File API, FormData, और Fetch API को अलग समझेंगे। फिर minimal upload, React preview और real progress, server validation, और S3 या Cloud Storage में कब जाना है, यह देखेंगे।

Storage design के लिए Claude Code और AWS S3 भी पढ़ें। Security review के लिए Claude Code security best practices उपयोगी है।

Browser side को तीन हिस्सों में समझें

पहला हिस्सा File API है। जब user<input type="file">से file चुनता है या drag and drop करता है, browser हमेंFileobject देता है। इसमेंfile.name, file.size, file.type, औरfile.lastModifiedमिलते हैं। Image preview के लिएURL.createObjectURL(file)से temporary URL बनाया जा सकता है।

दूसरा हिस्सा FormData है। यह fields और files कोmultipart/form-dataके रूप में server तक भेजता है। आम pattern हैformData.append("file", file)। एक practical बात: FormData के साथ fetch use करते समयContent-Typeheader खुद set न करें। Browser multipart boundary जोड़ता है; manual header request तोड़ सकता है।

तीसरा हिस्सा fetch है। Simple upload के लिए यह काफी है:

const formData = new FormData();
formData.append("file", file);

await fetch("/api/upload", {
  method: "POST",
  body: formData
});

लेकिन real upload progress चाहिए तो fetch अकेला convenient नहीं है। Progress event के लिएXMLHttpRequest.upload.onprogressअब भी practical है। इसलिए Claude Code को साफ लिखें: simple upload के लिए fetch, real progress bar के लिए XMLHttpRequest।

HTML और fetch से minimal implementation

React और S3 से पहले base flow बनाइए: file select, browser validation, image preview, FormData send।

<form id="upload-form">
  <input id="file-input" name="file" type="file" accept="image/png,image/jpeg,application/pdf" />
  <button type="submit">Upload</button>
</form>
<img id="preview" alt="" style="max-width: 240px; display: none;" />
<p id="message"></p>

<script type="module">
  const MAX_BYTES = 5 * 1024 * 1024;
  const allowedTypes = new Set(["image/png", "image/jpeg", "application/pdf"]);
  const form = document.querySelector("#upload-form");
  const input = document.querySelector("#file-input");
  const preview = document.querySelector("#preview");
  const message = document.querySelector("#message");

  input.addEventListener("change", () => {
    const file = input.files?.[0];
    preview.style.display = "none";
    preview.removeAttribute("src");
    message.textContent = "";

    if (!file) return;
    if (!allowedTypes.has(file.type)) {
      message.textContent = "केवल PNG, JPEG और PDF allowed हैं।";
      input.value = "";
      return;
    }
    if (file.size > MAX_BYTES) {
      message.textContent = "File 5MB या उससे कम होनी चाहिए।";
      input.value = "";
      return;
    }
    if (file.type.startsWith("image/")) {
      preview.src = URL.createObjectURL(file);
      preview.style.display = "block";
      preview.onload = () => URL.revokeObjectURL(preview.src);
    }
  });

  form.addEventListener("submit", async (event) => {
    event.preventDefault();
    const file = input.files?.[0];
    if (!file) {
      message.textContent = "पहले file चुनिए।";
      return;
    }

    const formData = new FormData();
    formData.append("file", file);

    const response = await fetch("/api/upload", {
      method: "POST",
      body: formData
    });

    const result = await response.json();
    message.textContent = response.ok ? `Saved: ${result.name}` : result.error;
  });
</script>

Client-side validation user experience के लिए है, security के लिए नहीं। कोई भी custom client इस JavaScript को bypass कर सकता है। Server को MIME, extension, size, storage name और storage location फिर से validate करना होगा।

React preview और real progress

React में state अलग रखें: selected file, preview URL, progress, error, saved filename। इससे Claude Code बाद में drag and drop, cancel या retry जोड़ पाएगा।

import { ChangeEvent, FormEvent, useEffect, useMemo, useState } from "react";

const MAX_BYTES = 5 * 1024 * 1024;
const ALLOWED_TYPES = new Set(["image/png", "image/jpeg", "application/pdf"]);

type UploadResult = { ok: true; name: string; size: number; type: string };

export function FileUploadBox() {
  const [selectedFile, setSelectedFile] = useState<File | null>(null);
  const [previewUrl, setPreviewUrl] = useState<string | null>(null);
  const [progress, setProgress] = useState(0);
  const [error, setError] = useState<string | null>(null);
  const [uploadedName, setUploadedName] = useState<string | null>(null);
  const canUpload = useMemo(() => selectedFile && !error, [selectedFile, error]);

  useEffect(() => {
    return () => {
      if (previewUrl) URL.revokeObjectURL(previewUrl);
    };
  }, [previewUrl]);

  function handleFileChange(event: ChangeEvent<HTMLInputElement>) {
    const file = event.target.files?.[0] ?? null;
    setUploadedName(null);
    setProgress(0);
    setError(null);
    if (previewUrl) URL.revokeObjectURL(previewUrl);
    setPreviewUrl(null);

    if (!file) return setSelectedFile(null);
    if (!ALLOWED_TYPES.has(file.type)) {
      setSelectedFile(null);
      return setError("केवल PNG, JPEG और PDF allowed हैं।");
    }
    if (file.size > MAX_BYTES) {
      setSelectedFile(null);
      return setError("File 5MB या उससे कम होनी चाहिए।");
    }
    setSelectedFile(file);
    if (file.type.startsWith("image/")) setPreviewUrl(URL.createObjectURL(file));
  }

  async function handleSubmit(event: FormEvent<HTMLFormElement>) {
    event.preventDefault();
    if (!selectedFile) return;
    const formData = new FormData();
    formData.append("file", selectedFile);
    const result = await uploadWithProgress(formData, setProgress);
    setUploadedName(result.name);
  }

  return (
    <form onSubmit={handleSubmit} className="space-y-4">
      <input type="file" accept="image/png,image/jpeg,application/pdf" onChange={handleFileChange} />
      {previewUrl && <img src={previewUrl} alt="File preview" width={240} />}
      {selectedFile && <p>{selectedFile.name} / {Math.round(selectedFile.size / 1024)}KB</p>}
      {error && <p role="alert">{error}</p>}
      <progress value={progress} max={100}>{progress}%</progress>
      <button type="submit" disabled={!canUpload}>Upload</button>
      {uploadedName && <p>Saved: {uploadedName}</p>}
    </form>
  );
}

function uploadWithProgress(formData: FormData, onProgress: (progress: number) => void) {
  return new Promise<UploadResult>((resolve, reject) => {
    const xhr = new XMLHttpRequest();
    xhr.open("POST", "/api/upload");
    xhr.upload.addEventListener("progress", (event) => {
      if (event.lengthComputable) onProgress(Math.round((event.loaded / event.total) * 100));
    });
    xhr.addEventListener("load", () => {
      const body = JSON.parse(xhr.responseText || "{}");
      if (xhr.status >= 200 && xhr.status < 300) resolve(body);
      else reject(new Error(body.error ?? "Upload failed"));
    });
    xhr.addEventListener("error", () => reject(new Error("Network error")));
    xhr.send(formData);
  });
}

Progress display ईमानदार होना चाहिए। अगर असली progress नहीं माप रहे हैं, तो केवल “uploading” दिखाइए। Fake progress bar slow network पर भरोसा तोड़ती है।

Server validation को double-check करें

यह Next.js Route Handler local.local-uploadsमें save करता है। Production में इसे S3, Cloud Storage, Azure Blob Storage या R2 adapter से बदलें।

// app/api/upload/route.ts
import { randomUUID } from "node:crypto";
import { mkdir, writeFile } from "node:fs/promises";
import path from "node:path";
import { NextRequest, NextResponse } from "next/server";

export const runtime = "nodejs";

const MAX_BYTES = 5 * 1024 * 1024;
const ALLOWED_TYPES = new Map([
  ["image/png", ".png"],
  ["image/jpeg", ".jpg"],
  ["application/pdf", ".pdf"]
]);

export async function POST(request: NextRequest) {
  const formData = await request.formData();
  const value = formData.get("file");
  if (!(value instanceof File)) {
    return NextResponse.json({ error: "File missing है।" }, { status: 400 });
  }

  const expectedExt = ALLOWED_TYPES.get(value.type);
  const originalExt = path.extname(value.name).toLowerCase();
  if (!expectedExt) return NextResponse.json({ error: "MIME type allowed नहीं है।" }, { status: 400 });
  if (value.size === 0 || value.size > MAX_BYTES) {
    return NextResponse.json({ error: "File 1 byte से 5MB के बीच होनी चाहिए।" }, { status: 400 });
  }
  if (expectedExt === ".jpg" && ![".jpg", ".jpeg"].includes(originalExt)) {
    return NextResponse.json({ error: "JPEG extension invalid है।" }, { status: 400 });
  }
  if (expectedExt !== ".jpg" && originalExt !== expectedExt) {
    return NextResponse.json({ error: "MIME और extension match नहीं करते।" }, { status: 400 });
  }

  const bytes = Buffer.from(await value.arrayBuffer());
  const storedName = `${randomUUID()}${expectedExt === ".jpg" ? ".jpg" : expectedExt}`;
  const uploadDir = path.join(process.cwd(), ".local-uploads");
  await mkdir(uploadDir, { recursive: true });
  await writeFile(path.join(uploadDir, storedName), bytes, { flag: "wx" });
  return NextResponse.json({ ok: true, name: storedName, size: value.size, type: value.type });
}

कम से कम MIME, extension, size, storage name और storage location check करें। Sensitive documents के लिए magic number, image decoding, antivirus scan, authentication, quota और audit log जोड़ें।

S3 या Cloud Storage कब चुनें

Local storage learning और prototype के लिए ठीक है। अगर file customer asset है, server multiple हो सकते हैं, lifecycle rules चाहिए, private download चाहिए, या async image processing चाहिए, तो object storage पर जाएँ।

शुरुआत में direct browser-to-S3 upload जरूरी नहीं है। छोटे files के लिए server receive, validate और फिर S3 में save कर सकता है। Presigned URL तब लाएँ जब file size, traffic या server bandwidth real issue बने।

Existing Next.js local file upload को S3 storage में बदलें।
Current: /api/upload FormData receive करता है और MIME, extension, size validate करता है।
Rules: original filename को S3 key न बनाएं। uploads/yyyy/mm/dd/{uuid}.ext में save करें।
Rules: सिर्फ image/png, image/jpeg, application/pdf allow करें। Max 5MB।
Rules: bucket private रहे। API public URL नहीं, file ID लौटाए।
Deliverables: app/api/upload/route.ts, lib/storage/s3.ts, failure tests, README env vars।
Verify: oversized file, extension mismatch, unauthenticated user, successful upload tests समझाएँ।

Practical examples और failures

Example 1 profile image है। Preview, crop, retry और thumbnail जरूरी हैं। Beginner setup में PNG, JPEG, WebP रखें; SVG को बिना review allow न करें।

Example 2 CSV import है। Upload सिर्फ entry point है। Columns, encoding, row count, duplicate records, rollback और error report असली काम हैं।

Example 3 invoice या contract PDF है। Upload के बाद public URL न दें। Private storage में save करें, user और organization permission check करें, फिर short-lived signed URL दें।

Common failures हैं: acceptको security समझना, original filename save करना, public URL जल्दी लौटाना, fake progress दिखाना, और S3 permissions बहुत broad रखना।

Claude Code prompt और Masa note

इस Next.js app में सुरक्षित file upload जोड़ें।
Goal: SaaS admin screen में एक बार में एक PNG/JPEG/PDF upload करना।
Client: React component File API use करे; filename, size, image preview, error, upload state दिखाए।
Transport: FormData से /api/upload पर POST करें। Real progress चाहिए तो XMLHttpRequest use करें और बताएं fetch क्यों नहीं।
Server: app/api/upload/route.ts में FormData receive करें और MIME, extension, 5MB limit, empty file validate करें।
Storage: original filename को saved name न बनाएं। UUID + extension से .local-uploads में save करें।
Forbidden: public/ में direct save न करें। extension check को full security न कहें। S3 bucket public न बनाएं।
Verification: oversized, extension mismatch, missing file, successful upload tests समझाएँ।
References: MDN File API, FormData, Fetch API।

मेरे परीक्षण में सबसे बड़ा सुधार तब आया जब मैंने “fetch वाला simple upload” और “real progress वाला upload” अलग लिखवाया। बिना यह लिखे Claude Code कभी-कभी सुंदर progress bar बनाता है, पर वह actual network progress से जुड़ा नहीं होता। Local storage से शुरू करना भी बेहतर रहा: File API, FormData, validation और preview stabilize हो जाते हैं, फिर storage adapter को S3 से बदलना आसान होता है।

Summary

Secure file upload सिर्फ input नहीं है। यह user device, application server, storage और permission model के बीच boundary है। Claude Code इसे तेज बना सकता है, लेकिन prompt में File API, FormData, server validation, size limit, generated storage key, honest progress और private object storage स्पष्ट होना चाहिए।

अगर आप यह pattern real repository में लागू करना चाहते हैं, तो Claude Code training और consultation में upload flow, S3, signed URL, tests और review checklist साथ में बनाए जा सकते हैं। Free PDF और learning materials से शुरुआत करना भी अच्छा रास्ता है।

#Claude Code #file upload #FormData #S3 #React #security
मुफ़्त

मुफ़्त PDF: Claude Code cheatsheet

Email डालें और commands, review habits तथा safe workflow वाली एक-page PDF पाएँ.

हम आपका data सुरक्षित रखते हैं और spam नहीं भेजते.

Masa

लेखक के बारे में

Masa

Claude Code workflow और team adoption पर काम करने वाला engineer.