Vue Storefront is now Alokai! Learn More
Middleware

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

  1. File Size: Always set appropriate file size limits to prevent server overload
  2. File Types: Restrict allowed MIME types to prevent security vulnerabilities
  3. File Count: Limit the number of files that can be uploaded simultaneously
  4. 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:

  1. Prepare your file(s) as File or Blob objects
  2. Configure the request with the correct content type
  3. 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

  1. Content Type: Always set the Content-Type header to multipart/form-data for file uploads.
  2. File Objects: The SDK automatically handles File and Blob objects in the request parameters.
  3. Request Configuration: Use prepareConfig() to set the correct headers and method for your upload request.
  4. Error Handling: Always implement proper error handling for upload operations as they can fail for various reasons (file size limits, network issues, etc.).