Middleware
The middleware provides built-in support for file uploads using multer
. This guide explains how to implement and configure file uploads in your middleware application.
File upload is disabled by default for performance reasons. You need to explicitly enable it in your configuration to use this feature.
Configuration
The file upload service can be configured through the FileUploadOptions
interface:
interface FileUploadOptions {
enabled?: boolean;
maxFileSize?: number; // in bytes
maxFiles?: number;
allowedMimeTypes?: string[];
fieldNames?: string[];
}
Default configuration:
- Maximum file size: 5MB
- Maximum number of files: 5
- Allowed MIME types:
["image/*", "application/pdf"]
Header-Based File Upload Configuration
For performance reasons, file uploads are disabled by default. It is recommended to enable them only when needed and use headers to control file upload behavior. This approach allows for dynamic configuration based on request headers, which is particularly useful for enabling uploads only for specific scenarios, such as authenticated requests or requests with specific headers.
Here's an example of dynamic configuration based on request headers:
const app = await createServer(
{ integrations: config.integrations },
{
fileUpload: (req) => ({
enabled: req.headers["x-enable-upload"] === "true",
maxFileSize: req.headers["x-upload-size"]
? parseInt(req.headers["x-upload-size"])
: 5242880, // Default to 5MB
maxFiles: 5,
allowedMimeTypes: ["image/*", "application/pdf"],
fieldNames: [],
}),
}
);
In this example:
- File uploads are only enabled when the
x-enable-upload: true
header is present. - The maximum file size can be controlled via the
x-upload-size
header. - Other options remain static but could also be made dynamic based on your needs.
This method is preferred as it provides flexibility and control over file upload behavior, ensuring that uploads are only enabled when necessary and under specific conditions.
Implementation Example
Here's how to implement a file upload endpoint that connects with the SDK example:
// api/custom-methods/uploadFile.ts
export async function uploadFile(
context: IntegrationContext
): Promise<CustomMethodResponse> {
const files = context.req.files;
if (!files) {
throw new Error("No files were uploaded");
}
const client = await context.getApiClient("commerce");
const formData = new FormData();
// Handle both multiple files and single file uploads
if (Array.isArray(files)) {
files.forEach((file) => {
formData.append("files", new Blob([file.buffer]), file.originalname);
});
}
// Send files to your external service endpoint
const response = await client.api.post(
"/your-external-service/upload",
formData,
{
headers: {
"Content-Type": "multipart/form-data",
},
}
);
return response.data;
}
Custom Configuration
You can customize the file upload behavior by modifying the middleware options:
// apps/storefront-middleware/src/index.ts
const app = await createServer(config, {
// Custom file upload configuration
fileUpload: {
enabled: true,
maxFileSize: 10 * 1024 * 1024, // 10MB
maxFiles: 10,
allowedMimeTypes: ["image/*", "application/pdf", "text/plain"],
fieldNames: ["avatar", "documents"], // Specific field names to accept
},
// ...
});
Additional Parameters
When uploading files, you can include additional parameters alongside your files. These parameters can be accessed in your middleware implementation:
// api/custom-methods/uploadFile.ts
export async function uploadFile(
context: IntegrationContext,
additionalParams: Record<string, unknown>
): Promise<CustomMethodResponse> {
const files = context.req.files;
if (!files) {
throw new Error("No files were uploaded");
}
const client = await context.getApiClient("commerce");
const formData = new FormData();
// Add additional parameters to formData
Object.entries(additionalParams).forEach(([key, value]) => {
formData.append(key, value);
});
// Handle files
if (Array.isArray(files)) {
files.forEach((file) => {
formData.append("files", new Blob([file.buffer]), file.originalname);
});
}
// Send files and additional parameters to your external service
const response = await client.api.post(
"/your-external-service/upload",
formData,
{
headers: {
"Content-Type": "multipart/form-data",
},
}
);
return response.data;
}
Security Considerations
- File Size: Always set appropriate file size limits to prevent server overload
- File Types: Restrict allowed MIME types to prevent security vulnerabilities
- File Count: Limit the number of files that can be uploaded simultaneously
- Validation: The middleware performs automatic validation based on your configuration
The maximum file size is capped at 10MB regardless of configuration for security purposes.
Usage with SDK
The SDK supports file uploads using multipart/form-data
requests. This guide explains how to implement file uploads in your application using TypeScript.
Basic Usage
To upload files using the SDK, you need to:
- Prepare your file(s) as
File
orBlob
objects - Configure the request with the correct content type
- Call your upload endpoint using the SDK
Here's a basic example:
const file = new File(["content"], "test.txt", { type: "text/plain" });
const response = await sdk.commerce.uploadFile(
{ file },
prepareConfig({
headers: {
"Content-Type": "multipart/form-data",
},
})
);
Multiple Files Upload
The SDK supports uploading multiple files in a single request:
const files = [
new File(["content1"], "file1.txt", { type: "text/plain" }),
new File(["content2"], "file2.txt", { type: "text/plain" }),
];
const response = await sdk.commerce.uploadFile(
{ files },
prepareConfig({
headers: {
"Content-Type": "multipart/form-data",
},
})
);
Additional Parameters
The SDK allows you to send additional parameters along with your files. This is useful when you need to include metadata or other information with your upload:
interface UploadParams {
category: string;
description: string;
tags: string[];
}
const response = await sdk.commerce.uploadFile(
{
files, // Your file or files array
metadata: {
category: "products",
description: "Product images batch upload",
tags: ["products", "images"],
},
visibility: "public",
expiresIn: "30d",
},
prepareConfig({
headers: {
"Content-Type": "multipart/form-data",
},
})
);
The SDK will automatically handle both the file data and additional parameters in the request. Here are some common use cases:
// Single file with metadata
const response = await sdk.commerce.uploadFile(
{
file,
category: "avatars",
userId: "user123",
},
prepareConfig({
headers: {
"Content-Type": "multipart/form-data",
},
})
);
// Multiple files with shared parameters
const response = await sdk.commerce.uploadFile(
{
files: [file1, file2, file3],
batchId: "batch123",
processImmediately: true,
},
prepareConfig({
headers: {
"Content-Type": "multipart/form-data",
},
})
);
All additional parameters will be automatically included in the multipart/form-data request. You can include any serializable data alongside your files.
Important Notes
- Content Type: Always set the
Content-Type
header tomultipart/form-data
for file uploads. - File Objects: The SDK automatically handles
File
andBlob
objects in the request parameters. - Request Configuration: Use
prepareConfig()
to set the correct headers and method for your upload request. - Error Handling: Always implement proper error handling for upload operations as they can fail for various reasons (file size limits, network issues, etc.).