Error 400 Bad Request saat mengunggah berkas dari aplikasi React Native sering kali disebabkan oleh hilangnya parameter boundary pada header Content-Type: multipart/form-data. Masalah ini menyebabkan stream parser di sisi backend gagal mengidentifikasi pemisah data (delimiter), yang berujung pada penolakan request atau parser timeout.

Gejala dan Investigasi Error di Backend

Saat request upload berkas dikirim dari React Native menggunakan Fetch API atau Axios, backend yang menggunakan parser seperti Multer, Busboy, atau Formidable umumnya menghasilkan log error berikut:

Error: Multipart: Boundary not found
    at Busboy.write (/app/node_modules/busboy/lib/types/multipart.js:84:11)
    at ...

Pada arsitektur tertentu, jika parser tidak melempar error secara langsung, stream backend akan terus menunggu delimiter masuk hingga connection timeout (HTTP 408 atau 504 Gateway Timeout).

Untuk mengonfirmasi akar masalah, periksa raw header yang diterima oleh server backend:

// Contoh inspeksi pada handler Express.js
app.post('/api/upload', (req, res, next) => {
  console.log('Incoming Content-Type:', req.headers['content-type']);
  next();
});

Jika output hanya menampilkan multipart/form-data tanpa parameter pelengkap, stream parser tidak dapat memproses payload:

// Header tidak valid:
Content-Type: multipart/form-data

// Header valid sesuai RFC 7578:
Content-Type: multipart/form-data; boundary=---------------------------974767299852498929531610575

Akar Masalah: Penimpaan Manual Content-Type

Sesuai spesifikasi RFC 7578, request multipart/form-data wajib menyertakan atribut boundary sebagai penanda awal dan akhir setiap field atau file chunk. Pada React Native, runtime native (RCTNetworking di iOS dan OkHttp di Android) bertugas mendeteksi objek FormData dan menyusun boundary acak secara otomatis.

Akar masalah muncul ketika developer mendefinisikan header Content-Type: multipart/form-data secara manual pada konfigurasi Fetch atau Axios:

// KODE BERMASALAH: Penimpaan manual
await fetch('https://api.example.com/upload', {
  method: 'POST',
  headers: {
    'Content-Type': 'multipart/form-data', // Mengunci string literal dan menghapus boundary otomatis
    'Authorization': `Bearer ${token}`,
  },
  body: formData,
});

Penimpaan manual ini menginstruksikan modul networking native untuk mempertahankan string literal tersebut dan membatalkan injeksi parameter boundary.

Solusi pada Sisi React Native

Langkah perbaikan utama adalah menghapus deklarasi manual Content-Type dan membiarkan runtime menentukan boundary secara dinamis.

1. Perbaikan pada Fetch API

Hapus baris Content-Type dari opsi headers. Pastikan format objek berkas sesuai dengan standar React Native:

const formData = new FormData();
formData.append('file', {
  uri: fileUri,
  name: 'document.pdf',
  type: 'application/pdf',
} as any);

const response = await fetch('https://api.example.com/upload', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${token}`,
    // Biarkan runtime mengisi Content-Type dan boundary secara otomatis
  },
  body: formData,
});

2. Perbaikan pada Axios

Axios sering kali memiliki interceptor atau default headers global yang menetapkan Content-Type: application/json. Jika Anda menimpanya menjadi multipart/form-data, boundary tetap akan hilang. Hapus header tersebut khusus untuk request FormData:

import axios from 'axios';

const uploadData = new FormData();
uploadData.append('file', {
  uri: fileUri,
  name: 'photo.jpg',
  type: 'image/jpeg',
} as any);

// Cara 1: Jangan override Content-Type
await axios.post('https://api.example.com/upload', uploadData, {
  headers: {
    'Authorization': `Bearer ${token}`,
  },
});

// Cara 2: Hapus header jika ada axios interceptor global
const headers = { 'Authorization': `Bearer ${token}` };
delete headers['Content-Type'];

await axios.post('https://api.example.com/upload', uploadData, { headers });

Catatan: Pada Android, field uri harus mempertahankan prefix file (misalnya file://... atau content://...), sedangkan type harus berupa MIME type valid agar modul native dapat membaca stream biner berkas.

Hardening Backend: Validasi Header RFC 7578

Untuk mencegah server menggantung (hang) atau melempar error unhandled saat menerima request tanpa boundary, pasang middleware validasi sebelum parser multipart dieksekusi.

import { Request, Response, NextFunction } from 'express';

export function validateMultipartBoundary(req: Request, res: Response, next: NextFunction) {
  const contentType = req.headers['content-type'];

  if (contentType && contentType.startsWith('multipart/form-data')) {
    const hasBoundary = contentType.includes('boundary=');
    
    if (!hasBoundary) {
      return res.status(400).json({
        error: 'Bad Request',
        message: 'RFC 7578 violation: multipart/form-data requires a valid boundary parameter.',
      });
    }
  }

  next();
}

Gunakan middleware ini sebelum multer atau parser streaming lainnya pada routing Anda:

import express from 'express';
import multer from 'multer';
import { validateMultipartBoundary } from './middleware/validateMultipart';

const app = express();
const upload = multer({ dest: 'uploads/' });

app.post(
  '/api/upload',
  validateMultipartBoundary,
  upload.single('file'),
  (req, res) => {
    res.status(200).json({ status: 'success', file: req.file });
  }
);

Implementasi validasi awal ini menjamin penolakan dini (fail-fast) dengan respons diagnostik yang jelas jika klien masih mengirimkan request cacat, sekaligus melindungi resource I/O parser backend.