Belajar GraphQL - Upload File dengan GraphQL Upload
Episode 17 of 51

Belajar GraphQL - Upload File dengan GraphQL Upload

Episode 17 menangani upload file di GraphQL: spesifikasi GraphQL Upload dengan multipart request, implementasi graphql-upload di Apollo Server, pemrosesan stream dengan validasi tipe dan ukuran, integrasi AWS S3 dan Cloudinary, hingga keamanan file upload.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Pendahuluan

File bukan data biasa — ukurannya besar, berformat khusus, dan membutuhkan penanganan streaming agar tidak membebani memori server. Episode 17 membahas upload file di GraphQL dengan spesifikasi GraphQL Upload yang modern.

Kita akan membandingkan pendekatan upload, mengimplementasikan graphql-upload di Apollo Server, memproses file dengan validasi tipe dan ukuran, mengintegrasikan penyimpanan seperti AWS S3 dan Cloudinary, serta membahas keamanan upload file.

File Upload Approaches

GraphQL Upload dan Multipart

Cara paling praktis adalah GraphQL Upload specification: file dikirim sebagai multipart/form-data bersama dengan operasi GraphQL dalam satu request. Server mengekspos scalar Upload, dan client mengirim file sebagai variabel yang tipe-nya Upload.

Pendekatan lain yang perlu kalian kenali:

  • Base64 encoding: mengubah file menjadi string base64 di dalam JSON — sangat tidak direkomendasikan, payload membesar sepertiga dan seluruh file termuat di memori.
  • Pre-signed URLs: client meng-upload langsung ke object storage (S3, GCS), dan GraphQL hanya menerima referensi URL-nya. Ini paling efisien untuk file besar dan dianjurkan untuk production skala besar.

Implementasi graphql-upload

Instalasi dan Scalar Upload

Pasang package graphql-upload dengan npm install graphql-upload:

Install graphql-upload
npm install graphql-upload

Di schema, tambahkan scalar dan gunakan pada mutation:

Schema dengan Upload
scalar Upload
 
type Mutation {
  uploadAvatar(file: Upload!): User!
  uploadPostImages(files: [Upload!]!): Post!
}

Integrasi dengan Apollo Server

Apollo Server 4 tidak lagi menyertakan middleware upload bawaan, jadi pasang graphqlUploadExpress secara manual saat server HTTP:

JSMiddleware upload di Express
import { graphqlUploadExpress } from "graphql-upload/express.js";
import { ApolloServer } from "@apollo/server";
import { expressMiddleware } from "@apollo/server/express4";
import express from "express";
 
const app = express();
app.use("/graphql", graphqlUploadExpress({ maxFileSize: 5 * 1024 * 1024, maxFiles: 5 }));
app.use("/graphql", expressMiddleware(server));
 
app.listen(4000);

Parameter maxFileSize dan maxFiles adalah garis pertahanan pertama terhadap penyalahgunaan.

File Processing

Stream Handling dan Validasi

File dari graphql-upload adalah stream (berupa objek FileUpload). Jangan baca seluruh file ke memori untuk file besar — proses per bagian atau stream langsung ke penyimpanan:

JSResolver upload dengan validasi
import { Readable } from "stream";
 
async function uploadAvatar(_, args, ctx) {
  const { createReadStream, mimetype, filename } = await args.file;
 
  const allowed = ["image/jpeg", "image/png", "image/webp"];
  if (!allowed.includes(mimetype)) {
    throw new Error("Tipe file tidak diizinkan");
  }
  if (filename.length > 100) {
    throw new Error("Nama file terlalu panjang");
  }
 
  const stream = createReadStream();
  const key = await ctx.storage.uploadStream(stream, { contentType: mimetype });
 
  return ctx.userService.updateAvatar(ctx.user.id, key);
}

Validasi mimetype dan ukuran dilakukan sebelum file diproses. Untuk gambar, olah dulu dengan sharp — misalnya meresize avatar menjadi 128x128 dan mengubah format ke webp — sehingga file yang disimpan terkontrol:

Install sharp
npm install sharp
JSOlah gambar dengan sharp
import sharp from "sharp";
 
const buffer = await sharp(await streamToBuffer(stream))
  .resize(128, 128)
  .webp({ quality: 80 })
  .toBuffer();

Ingat: jangan pernah percaya nilai mimetype yang dikirim client — deteksi tipe sebenarnya dari isi file, misalnya dengan file-type, karena mimetype bisa dipalsukan.

Storage Solutions

AWS S3 dan Cloudinary

Menulis file ke filesystem lokal hanya cocok untuk development — storage lokal tidak terdistribusi dan hilang saat instance di-recreate. Untuk production gunakan object storage:

Install AWS SDK
npm install @aws-sdk/client-s3
JSUpload ke S3
import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
 
const s3 = new S3Client({ region: process.env.AWS_REGION });
 
async function uploadToS3(key, body) {
  await s3.send(new PutObjectCommand({
    Bucket: process.env.S3_BUCKET,
    Key: key,
    Body: body,
  }));
  return `https://cdn.kalian.com/${key}`;
}

Alternatif yang populer: Cloudinary untuk gambar dengan transformasi on-the-fly, dan Google Cloud Storage sebagai padanan GCP. Database BLOB storage tidak direkomendasikan — file besar menyumbat database dan sulit di-backup.

Keamanan File Upload

Checklist Keamanan

Upload file adalah vektor serangan yang sangat populer. Terapkan checklist berikut:

  • Validasi tipe: deteksi tipe dari isi, bukan hanya mimetype.
  • Batas ukuran: maxFileSize di middleware dan validasi tambahan di resolver.
  • Rate limiting: batasi jumlah upload per user (episode 15).
  • Deteksi file berbahaya: scan dengan antivirus pada file yang di-upload user publik.
  • Nama file aman: jangan pernah memakai nama file asli langsung; generate key acak.
  • Blokir eksekusi: sajikan file dari domain terpisah atau CDN, bukan dari server aplikasi.
JSNama file acak yang aman
import crypto from "crypto";
 
const key = `${Date.now()}-${crypto.randomUUID()}.webp`;

Penutup

Inti yang harus dibawa pulang:

  • Gunakan spesifikasi GraphQL Upload dengan multipart untuk file, atau pre-signed URL untuk file besar.
  • Scalar Upload ditambahkan ke schema dan middleware graphqlUploadExpress dipasang manual di Apollo 4.
  • Proses file sebagai stream; olah gambar dengan sharp sebelum disimpan.
  • Validasi tipe dari isi file, bukan dari mimetype yang bisa dipalsukan.
  • Simpan file di object storage seperti S3, bukan di filesystem lokal atau BLOB database.
  • Terapkan batas ukuran, rate limit, scan antivirus, dan nama file acak.

Di episode 18 selanjutnya kalian akan mempelajari advanced schema design patterns — prinsip desain schema, desain relasi one-to-one sampai many-to-many, global object identification dengan Node interface, mutation payload design, strategi evolusi API dengan deprecation, hingga modularisasi schema. Schema kalian akan dirancang layaknya arsitek berpengalaman!

Belajar GraphQL - Upload File dengan GraphQL Upload | Belajar GraphQL