Episode ini membahas file upload dan media: memproses multipart form dengan multer, membatasi ukuran dan tipe file, streaming file dari disk dengan sendFile, serta mendukung range request untuk video dan audio.

Hampir setiap aplikasi modern perlu menerima file: foto profil, lampiran dokumen, atau video yang diunggah pengguna. Mengelola upload dengan benar bukan sekadar menyimpan byte — kalian harus memvalidasi isi, membatasi ukuran, dan mengalirkan file besar tanpa membebani memori.
Episode 17 membahas pengelolaan file dan media di Node.js: memproses multipart form dengan multer, membatasi ukuran dan tipe file, mengalirkan file dari disk ke respons, dan mendukung range request agar video bisa diputar dari tengah. Inilah keterampilan yang dibutuhkan API media.
File tidak bisa dikirim sebagai JSON biasa. Browser mengirim file dalam multipart/form-data, format yang memisahkan field biasa dan file dalam satu request. Multer adalah middleware Express yang memproses format ini:
npm install multernpm install multer menambahkan middleware upload. Multer mem-parsing body multipart dan menyediakan file yang terupload lewat req.file atau req.files.
import multer from "multer";
const upload = multer({ dest: "uploads/" });
app.post("/api/upload", upload.single("gambar"), (req, res) => {
console.log(req.file.originalname);
res.status(201).json({
nama: req.file.originalname,
ukuran: req.file.size,
path: req.file.path,
});
});upload.single("gambar") menangkap satu file dari field bernama gambar. Setelah middleware berjalan, req.file berisi metadata file: originalname, size, dan path tempat file disimpan di disk. Untuk banyak file, gunakan upload.array("gambar", 5).
File yang tidak dibatasi adalah risiko keamanan dan penyalahgunaan resource. Multer memungkinkan validasi nama dan tipe, sekaligus batas ukuran di tingkat Express:
const upload = multer({
dest: "uploads/",
limits: { fileSize: 5 * 1024 * 1024 },
fileFilter: (req, file, cb) => {
if (file.mimetype === "image/png" || file.mimetype === "image/jpeg") {
cb(null, true);
} else {
cb(new Error("Tipe file tidak diizinkan"));
}
},
});limits: { fileSize: 5 * 1024 * 1024 } menolak file di atas 5 megabyte, dan fileFilter hanya menerima PNG dan JPEG. Ingat: mimetype bisa dipalsukan oleh klien — di production, validasi tambahan dengan membaca magic bytes file adalah praktik yang lebih aman (kita bahas di episode 20).
Saat batas terlampaui atau tipe ditolak, error dari multer dikirim ke error handler:
app.use((err, req, res, next) => {
if (err instanceof multer.MulterError) {
return res.status(400).json({ error: "Upload gagal: " + err.message });
}
next(err);
});err instanceof multer.MulterError mendeteksi error khusus multer seperti LIMIT_FILE_SIZE. Mengubahnya menjadi respons 400 yang jelas jauh lebih baik daripada membiarkan error mentah bocor ke klien.
Ketika mengirim file kembali ke klien, jangan baca seluruhnya ke memori. Gunakan stream atau res.sendFile yang mengalirkan file langsung dari disk:
import { createReadStream } from "node:fs";
app.get("/api/download/:nama", (req, res) => {
res.download("uploads/" + req.params.nama);
});res.download(path) mengirim file dengan header Content-Disposition yang memicu unduhan di browser, sementara res.sendFile menampilkannya inline. Keduanya menggunakan streaming di balik layar — file besar tidak pernah dimuat penuh ke memori. Sanitasi req.params.nama penting agar pengguna tidak bisa menembus jalur file lain, topik yang akan dibahas di episode 20.
app.get("/api/video/:nama", (req, res) => {
const stream = createReadStream("uploads/" + req.params.nama);
stream.pipe(res);
});createReadStream(...) membaca file dalam potongan kecil dan stream.pipe(res) mengalirkannya ke respons. Pendekatan ini menjaga memori server tetap rendah meski melayani video berukuran besar secara bersamaan.
Browser memutar video dengan mengirim range request — permintaan hanya sebagian file (misalnya byte 1000 sampai 5000). Tanpa dukungan range, video tidak bisa di-skip. Server yang benar harus memahami header Range dan membalas dengan status 206:
import { stat } from "node:fs/promises";
app.get("/api/video/:nama", async (req, res) => {
const berkas = "uploads/" + req.params.nama;
const info = await stat(berkas);
res.status(200);
res.setHeader("Content-Length", info.size);
res.setHeader("Accept-Ranges", "bytes");
res.setHeader("Content-Type", "video/mp4");
createReadStream(berkas).pipe(res);
});Header Accept-Ranges: bytes memberitahu browser bahwa server mendukung sebagian file. Dukungan penuh Range — mem-parsing header, membalas 206 Partial Content, dan memetakan posisi stream — bisa jadi rumit; dalam praktik, pertimbangkan middleware seperti express-serve-static-core atau CDN yang sudah menangani ini secara matang.
Inti yang harus dibawa pulang:
limits.fileSize dan fileFilter membatasi ukuran dan tipe file.res.download dan res.sendFile mengalirkan file dari disk.createReadStream dan pipe.Accept-Ranges dan 206 mendukung pemutaran video.Di episode 18 selanjutnya kita akan membahas testing API dan integrasi dengan tools modern — test runner bawaan node:test, menguji endpoint HTTP dengan supertest, mocking untuk isolasi, serta mengukur coverage. Kalian akan mengunci API kalian agar perubahan tidak merusak fitur.