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:
- The data is already a
FormDatainstance - 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 Pattern | Type 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
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:
/**
* 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: stringThe format: binary tells openapi-typescript to type the field appropriately.