chowbea-axios
Advanced

File Uploads

Handle file uploads and multipart/form-data requests.

chowbea-axios automatically detects and handles file uploads using multipart/form-data.

Automatic FormData Detection

The client automatically converts requests to FormData when:

  1. The data is already a FormData instance
  2. The endpoint path matches upload patterns (/upload, /upload-images, /files/upload)
// Automatically uses FormData
const { data, error } = await api.post("/users/{id}/upload-images", {
  images: fileList, // File[]
}, { id: "123" });

Type Inference for File Fields

The generated types automatically map file fields based on naming conventions:

Field PatternType Mapping
*image*File | Blob
*file*File | Blob
*attachment*File | Blob
*upload*File | Blob
*document*File | Blob
*photo*File | Blob
*video*File | Blob
*media*File | Blob

Array fields become File[].

Single File Upload

// From file input
const fileInput = document.querySelector<HTMLInputElement>("#file-input");
const file = fileInput?.files?.[0];

if (file) {
  const { data, error } = await api.post("/documents", {
    file: file,
    name: "My Document",
    description: "A test document",
  });
}

Multiple File Upload

// From file input with multiple
const fileInput = document.querySelector<HTMLInputElement>("#files-input");
const files = Array.from(fileInput?.files || []);

const { data, error } = await api.post("/gallery/upload-images", {
  images: files,
  albumId: "123",
});

React File Upload Example

FileUpload.tsx
import { useState } from "react";
import { api } from "./api/api.client";

function FileUpload() {
  const [files, setFiles] = useState<File[]>([]);
  const [uploading, setUploading] = useState(false);
  const [error, setError] = useState<string | null>(null);

  const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    if (e.target.files) {
      setFiles(Array.from(e.target.files));
    }
  };

  const handleUpload = async () => {
    if (files.length === 0) return;

    setUploading(true);
    setError(null);

    const { data, error } = await api.post("/files/upload", {
      files: files,
      category: "documents",
    });

    setUploading(false);

    if (error) {
      setError(error.message);
      return;
    }

    console.log("Uploaded:", data);
    setFiles([]);
  };

  return (
    <div>
      <input 
        type="file" 
        multiple 
        onChange={handleFileChange}
        disabled={uploading}
      />
      
      {files.length > 0 && (
        <div>
          <p>Selected: {files.map(f => f.name).join(", ")}</p>
          <button onClick={handleUpload} disabled={uploading}>
            {uploading ? "Uploading..." : "Upload"}
          </button>
        </div>
      )}
      
      {error && <p style={{ color: "red" }}>{error}</p>}
    </div>
  );
}

Manual FormData

For complete control, create FormData manually:

const formData = new FormData();
formData.append("file", file);
formData.append("name", "My Document");
formData.append("tags[]", "important");
formData.append("tags[]", "work");

// Pass FormData directly
const { data, error } = await api.post("/documents", formData);

Customizing FormData Conversion

The conversion logic is in api.client.ts. You can modify it:

api.client.ts
/**
 * Checks if the request should use multipart/form-data.
 */
function shouldUseFormData(path: string, data: unknown): boolean {
  if (data instanceof FormData) return true;
  
  // Add your custom patterns
  const formDataPatterns = [
    /\/upload-images$/,
    /\/upload$/,
    /\/files\/upload$/,
    /\/attachments$/,  // Custom: added
    /\/media$/,        // Custom: added
  ];
  
  return formDataPatterns.some((pattern) => pattern.test(path));
}

/**
 * Converts a plain object to FormData.
 */
function convertToFormData(data: Record<string, unknown>): FormData {
  const formData = new FormData();
  
  for (const [key, value] of Object.entries(data)) {
    if (value === undefined || value === null) continue;
    
    if (value instanceof File || value instanceof Blob) {
      formData.append(key, value);
    } else if (Array.isArray(value)) {
      for (const item of value) {
        if (item instanceof File || item instanceof Blob) {
          formData.append(key, item);
        } else {
          formData.append(key, String(item));
        }
      }
    } else if (typeof value === "object") {
      // Custom: Handle nested objects as JSON
      formData.append(key, JSON.stringify(value));
    } else {
      formData.append(key, String(value));
    }
  }
  
  return formData;
}

Upload Progress

For upload progress, pass an onUploadProgress callback:

const { data, error } = await api.post("/files/upload", 
  { file: largeFile },
  {
    onUploadProgress: (progressEvent) => {
      const percentCompleted = Math.round(
        (progressEvent.loaded * 100) / (progressEvent.total ?? 1)
      );
      console.log(`Upload progress: ${percentCompleted}%`);
      setProgress(percentCompleted);
    },
  }
);

File Validation

Validate files before upload:

function validateFile(file: File): string | null {
  // Size limit (5MB)
  const maxSize = 5 * 1024 * 1024;
  if (file.size > maxSize) {
    return "File too large. Maximum size is 5MB.";
  }
  
  // Allowed types
  const allowedTypes = ["image/jpeg", "image/png", "application/pdf"];
  if (!allowedTypes.includes(file.type)) {
    return "Invalid file type. Allowed: JPEG, PNG, PDF.";
  }
  
  return null;
}

const validationError = validateFile(file);
if (validationError) {
  setError(validationError);
  return;
}

const { data, error } = await api.post("/files/upload", { file });

OpenAPI Spec for File Uploads

Your OpenAPI spec should define file upload endpoints like this:

paths:
  /files/upload:
    post:
      operationId: uploadFile
      requestBody:
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                file:
                  type: string
                  format: binary
                description:
                  type: string
              required:
                - file
      responses:
        200:
          description: Upload successful
          content:
            application/json:
              schema:
                type: object
                properties:
                  id:
                    type: string
                  url:
                    type: string

The format: binary tells openapi-typescript to type the field appropriately.

Next Steps

On this page