sistem upload ke Google Drive lewat backend

admin

 Lanjut — aku akan membuatkan sistem upload ke Google Drive lewat backend. Di bawah ini kamu dapat:

  1. Penjelasan opsi (user Drive via OAuth vs server-owned Drive via Service Account).

  2. Struktur file backend.

  3. Kode lengkap Express (OAuth2 flow untuk mengizinkan user menyimpan file ke Drive mereka + endpoint upload) — siap dipakai.

  4. Instruksi .env, izin (scopes), dan contoh panggilan dari frontend (axios / curl).

  5. Catatan keamanan & tips deploy.

Kita langsung ke kode — pakai nodejs + express + googleapis.


1) Pilihan arsitektur singkat

  • A. Upload ke user's Google Drive — pengguna diminta memberi izin (OAuth2). File akan muncul di Drive pengguna. Perlu client_id, client_secret, redirect URI. Cocok kalau kamu ingin simpan file ke akun Google masing-masing user.

  • B. Upload ke server-owned Drive (Service Account) — file disimpan di drive milik service account (atau shared drive). Tidak meminta izin tiap user. Cocok kalau serverlah pemilik data. Perlu membuat service account key di Google Cloud Console.

Di contoh ini aku fokus pada A (user Drive via OAuth) karena biasanya itulah yang diminta saat "simpan ke Google Drive" dari frontend. Aku juga sertakan catatan tentang opsi Service Account di bagian akhir.


2) Struktur folder backend

backend/
├── server.js
├── package.json
├── .env
├── uploads/               # (opsional) temp store untuk multer
└── serviceAccountDrive.js # (opsional) helper untuk Service Account

3) Install dependency

Di folder backend/ jalankan:

npm init -y
npm install express express-session dotenv googleapis multer cors
  • googleapis -> akses Drive API

  • express-session -> menyimpan token sementara per user (demo). Untuk produksi gunakan DB.

  • multer -> support upload multipart/form-data (file)

  • cors, dotenv -> config


4) Buat file .env

Isi contoh:

PORT=5000
SESSION_SECRET=isi_rahasia_session
GOOGLE_CLIENT_ID=xxx.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=yyy
GOOGLE_REDIRECT_URI=http://localhost:5000/auth/drive/callback

Pastikan di Google Cloud Console: OAuth client type = Web application, dan tambahkan redirect URI persis http://localhost:5000/auth/drive/callback.


5) Kode lengkap server.js

Salin file ini ke backend/server.js:

// server.js
import express from "express";
import session from "express-session";
import dotenv from "dotenv";
import cors from "cors";
import multer from "multer";
import { google } from "googleapis";
import fs from "fs";
import path from "path";

dotenv.config();

const {
  GOOGLE_CLIENT_ID,
  GOOGLE_CLIENT_SECRET,
  GOOGLE_REDIRECT_URI,
  SESSION_SECRET,
  PORT = 5000,
} = process.env;

if (!GOOGLE_CLIENT_ID || !GOOGLE_CLIENT_SECRET || !GOOGLE_REDIRECT_URI) {
  console.warn("Pastikan GOOGLE_CLIENT_ID / SECRET / REDIRECT_URI di .env");
}

const app = express();
app.use(cors({
  origin: "http://localhost:5173", // ganti origin frontend-mu
  credentials: true,
}));
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

app.use(session({
  secret: SESSION_SECRET || "dev-secret",
  resave: false,
  saveUninitialized: true,
  cookie: { secure: false } // secure:true jika https
}));

// Setup OAuth2 client generator
function createOAuthClient() {
  return new google.auth.OAuth2(
    GOOGLE_CLIENT_ID,
    GOOGLE_CLIENT_SECRET,
    GOOGLE_REDIRECT_URI
  );
}

// STEP 1: Redirect user to consent page
app.get("/auth/drive", (req, res) => {
  const oauth2Client = createOAuthClient();
  // scope: drive.file allows app create files user has opened/created with the app
  const scopes = [
    "https://www.googleapis.com/auth/drive.file",
    "https://www.googleapis.com/auth/userinfo.email",
    "https://www.googleapis.com/auth/userinfo.profile"
  ];

  const url = oauth2Client.generateAuthUrl({
    access_type: "offline", // to get refresh_token (first consent)
    prompt: "consent",
    scope: scopes,
  });

  res.redirect(url);
});

// STEP 2: OAuth2 callback -> exchange code for tokens
app.get("/auth/drive/callback", async (req, res) => {
  const code = req.query.code;
  if (!code) return res.status(400).send("Missing code");

  try {
    const oauth2Client = createOAuthClient();
    const { tokens } = await oauth2Client.getToken(code);
    // simpan tokens di session - demo. Simpan di DB untuk produksi.
    req.session.driveTokens = tokens;
    // Option: dapatkan profile
    oauth2Client.setCredentials(tokens);
    const oauth2 = google.oauth2({ auth: oauth2Client, version: "v2" });
    const me = await oauth2.userinfo.get();

    // redirect ke frontend (bisa bawa info user) atau tampilkan sederhana
    // contoh: redirect dengan info kecil -> frontend bisa menyambung
    res.send(`
      <h3>Login Google Drive berhasil</h3>
      <p>Akun: ${me.data.email}</p>
      <p>Tutup jendela ini dan lanjutkan ke aplikasi.</p>
    `);
  } catch (err) {
    console.error("OAuth callback error", err);
    res.status(500).send("OAuth exchange gagal");
  }
});

/**
 * Upload endpoints:
 * - POST /upload-to-drive (multipart/form-data) with file field `file`
 * - POST /upload-json (application/json) -> upload JSON content as file
 */

// multer untuk file upload
const upload = multer({ dest: "uploads/" });

app.post("/upload-to-drive", upload.single("file"), async (req, res) => {
  // cek token di session
  const tokens = req.session.driveTokens;
  if (!tokens) return res.status(401).json({ error: "Belum terautentikasi ke Google Drive. Kunjungi /auth/drive" });

  try {
    const oauth2Client = createOAuthClient();
    oauth2Client.setCredentials(tokens);
    const drive = google.drive({ version: "v3", auth: oauth2Client });

    // if using multer, file is at req.file.path
    const filePath = req.file.path;
    const fileName = req.file.originalname;

    const response = await drive.files.create({
      requestBody: {
        name: fileName,
        mimeType: req.file.mimetype,
      },
      media: {
        mimeType: req.file.mimetype,
        body: fs.createReadStream(filePath),
      },
    });

    // hapus temp file
    fs.unlinkSync(filePath);

    res.json({ success: true, file: response.data });
  } catch (err) {
    console.error("Drive upload error:", err);
    res.status(500).json({ error: "Gagal upload ke Drive" });
  }
});

// Upload JSON/text payload as file
app.post("/upload-json", async (req, res) => {
  const tokens = req.session.driveTokens;
  if (!tokens) return res.status(401).json({ error: "Belum terautentikasi ke Google Drive. Kunjungi /auth/drive" });

  try {
    const { filename = `data-${Date.now()}.json`, content } = req.body;
    if (!content) return res.status(400).json({ error: "Missing content" });

    const oauth2Client = createOAuthClient();
    oauth2Client.setCredentials(tokens);
    const drive = google.drive({ version: "v3", auth: oauth2Client });

    // upload from buffer/string using Readable
    const { Readable } = await import("stream");
    const bufferStream = new Readable();
    bufferStream.push(typeof content === "string" ? content : JSON.stringify(content, null, 2));
    bufferStream.push(null);

    const response = await drive.files.create({
      requestBody: {
        name: filename,
        mimeType: "application/json",
      },
      media: {
        mimeType: "application/json",
        body: bufferStream,
      },
    });

    res.json({ success: true, file: response.data });
  } catch (err) {
    console.error("Drive upload json error:", err);
    res.status(500).json({ error: "Gagal upload JSON ke Drive" });
  }
});

app.get("/logout-drive", (req, res) => {
  req.session.driveTokens = null;
  res.json({ ok: true });
});

app.listen(PORT, () => {
  console.log(`Backend berjalan di http://localhost:${PORT}`);
});

Penjelasan singkat:

  • /auth/drive → redirect ke Google consent screen.

  • /auth/drive/callback → Google mengembalikan code, kita tukar jadi tokens (access + refresh), simpan ke req.session.

  • /upload-to-drive → upload file (multipart/form-data) ke Drive menggunakan token yang tersimpan di session.

  • /upload-json → upload JSON/text langsung (bila kamu mau simpan form data).


6) Contoh pemanggilan dari frontend

A. Langkah: Minta user login ke Drive (buka popup)

Dari frontend (React), tombol Simpan ke Drive arahkan ke:

// buka popup login ke Google Drive
window.open("http://localhost:5000/auth/drive", "_blank", "width=600,height=700");

User akan diminta login & memberi izin. Setelah sukses, session di server akan berisi tokens.

B. Upload file (axios + multipart)

import axios from "axios";

async function uploadFileToDrive(file) {
  const form = new FormData();
  form.append("file", file);

  const res = await axios.post("http://localhost:5000/upload-to-drive", form, {
    withCredentials: true, // penting: biar cookie session ikut terkirim
    headers: { "Content-Type": "multipart/form-data" },
  });
  return res.data;
}

Pastikan axios dipanggil dengan withCredentials: true dan cors di server mengizinkan credentials (pada contoh server aku atur origin dan credentials true).

C. Upload JSON (misal: data form registrasi)

async function uploadJsonToDrive(jsonObj) {
  const res = await axios.post("http://localhost:5000/upload-json", {
    filename: `registrasi-${Date.now()}.json`,
    content: jsonObj
  }, {
    withCredentials: true
  });
  return res.data;
}

7) Contoh curl (untuk upload-json, jika sudah login di session yang sama tidak mudah via curl — tapi contoh ilustratif)

Jika kamu punya cookie session yang valid, bisa curl dengan cookie:

curl -b "connect.sid=SESSION_COOKIE_VALUE" \
  -H "Content-Type: application/json" \
  -d '{"filename":"test.json", "content":{"a":1}}' \
  http://localhost:5000/upload-json

8) Opsi: Service Account (server-owned Drive)

Jika kamu ingin server menyimpan file ke drive sendiri (tidak meminta izin user), gunakan Service Account:

  • Buat service account di Google Cloud Console, generate JSON key.

  • Beri scope https://www.googleapis.com/auth/drive.

  • Gunakan google.auth.GoogleAuth atau JWT client untuk set credentials dari keyfile, lalu panggil Drive API seperti di atas.

  • Catatan: service account punya drive terpisah; untuk menaruh file ke Drive user, kamu butuh domain-wide delegation (G Suite/Workspace) — bukan opsi untuk publik.

Contoh singkat pemakaian service account (file serviceAccountDrive.js):

import { google } from "googleapis";
import path from "path";

export async function uploadWithServiceAccount(keyFilePath, filename, bufferOrStream, mimetype="application/json") {
  const auth = new google.auth.GoogleAuth({
    keyFile: keyFilePath,
    scopes: ["https://www.googleapis.com/auth/drive"]
  });
  const client = await auth.getClient();
  const drive = google.drive({ version: "v3", auth: client });

  const res = await drive.files.create({
    requestBody: { name: filename, mimeType: mimetype },
    media: { mimeType: mimetype, body: bufferOrStream }
  });
  return res.data;
}

9) Catatan keamanan & praktik terbaik

  • Jangan simpan client_secret di client. .env di server aman (tapi jangan commit).

  • Untuk produksi, simpan access/refresh tokens di database (terenskripsi) — jangan hanya di session memori.

  • Gunakan HTTPS (secure cookies).

  • Refresh token: akses token habis — gunakan oauth2Client.setCredentials({ refresh_token }) untuk refresh dan perbarui token.

  • Scope drive.file relatif terbatas (hanya file yang dibuat oleh app). Jika ingin akses luas, butuh scope lain dan review Google.

  • Untuk aplikasi publik/produksi: lakukan proses verifikasi OAuth di Google Cloud Console jika memakai sensitive scopes.


10) Mau aku tambahkan juga?

  • Script lengkap frontend React (modifikasi AuthFormWithDrive.jsx) supaya: buka popup /auth/drive, tunggu user login, lalu otomatis panggil /upload-json dengan axios + withCredentials.

  • Penyimpanan token di DB & integrasi JWT supaya user tidak harus login ulang.

  • Contoh Service Account end-to-end (upload & view) jika kamu pilih server-owned storage.

Mau lanjut ke mana?

  • 1: Tambah kode frontend untuk otomatisasi upload setelah login.

  • 2: Tambah contoh menyimpan tokens ke MongoDB & rute refresh token.

  • 3: Contoh Service Account lengkap.

Ketik angka pilihanmu — aku langsung kirim kodenya. 🚀

Tags

Posting Komentar

0 Komentar

Posting Komentar (0)
3/related/default