--- title: 'Taking the Backend Out of the Upload Path' excerpt: "GeekBye records your screen and saves the video to your Google Drive. The first version shipped every recording through GeekBye's own servers on the way there; a release later, the file went straight from your machine to Drive, and the backend was demoted to holding a single pointer. The interesting part is how little code the 'direct, resumable' version actually contains — because the resumability came from deleting a proxy, not from writing one." date: '2026-07-22' author: 'Steven' authorAvatar: '/images/blog/authors/steven.jpg' coverImage: '/images/blog/covers/taking-the-backend-out-of-the-upload-path.svg' tags: ['Engineering', 'Architecture', 'Desktop', 'GeekBye Releases'] keywords: - 'upload directly to google drive from electron app' - 'resumable upload googleapis node stream' - 'proxy file upload through backend vs direct to cloud' - 'google drive api files.create createReadStream resumable' - 'desktop app oauth loopback flow drive.file scope' - 'move large file upload off your own server' lastModified: '2026-07-22' tldr: "GeekBye's screen recorder captures with `MediaRecorder` in the renderer, streams the WebM to disk chunk by chunk in the main process, then uploads the finished file to the user's Google Drive. v1.8.11 (early access, admin-gated) uploaded it the obvious way: `fs.readFileSync` the whole video into memory and POST it to GeekBye's backend, which then pushed it to Drive — every recording byte transiting GeekBye's servers. v1.8.13 rerouted the data path to go **straight from the machine to Drive**: the backend now only creates a metadata row and is later told the resulting `driveUrl`; the video never touches it. The desktop app becomes a first-class Drive API client — it runs the OAuth loopback flow itself, stores tokens locally in a new `drive_auth` table, and refreshes them without the backend. The 'resumable uploads' in the changelog are real but not hand-rolled: instead of a single monolithic POST, `DriveService` hands a `createReadStream` to Google's `googleapis` SDK via `drive.files.create`, and the SDK opens a resumable session under the hood — no app-level chunk size, byte offset, or `308`/`Content-Range` code exists in GeekBye. The release also deleted an entire native dependency (an ffmpeg WebM→MP4 step added earlier the same week) and added an orphaned-folder rollback so a failed upload leaves nothing behind." keyTakeaways: - "The architectural move is a data-path change: recordings went from client → backend → Drive to client → Drive, demoting the backend from carrying every byte to holding a metadata row and a `driveUrl` pointer — the file's bytes never touch your servers" - "You can get resumable uploads without implementing the resumable protocol: `DriveService` passes a `createReadStream` as `media.body` to `googleapis`' `drive.files.create`, and the SDK chooses a resumable session — GeekBye's source contains no `uploadType`, `Content-Range`, `308`, chunk size, or offset tracker, because those live in the SDK" - 'Streaming beats buffering for large files: the old proxy path did `fs.readFileSync(filePath)` to load the whole recording into memory for one multipart POST; the direct path streams from disk, which is both lighter on memory and what lets the transfer resume instead of restart' - 'Delegating the transfer let the team delete code, not add it: the direct-upload release removed a whole ffmpeg WebM→MP4 native dependency (added earlier the same week) and replaced a bespoke proxy with an SDK call — the reliability win came from subtraction' - 'A direct client needs its own lifecycle handling: because the Drive client is a singleton that loses its in-memory OAuth client on app restart, `CloudUploader` re-initializes `DriveService` from the locally stored tokens before every upload, and a failed upload rolls back the per-recording Drive folder it created so no empty folders pile up' --- GeekBye can record your screen — video, system audio, and mic — and drop the finished recording into your Google Drive automatically. Between v1.8.11 and v1.8.13, the feature's user-facing description barely changed, but the path the video takes to get to Drive was rerouted completely. In the first version, your recording traveled through GeekBye's servers. In the second, it went straight from your machine to your Drive, and GeekBye's backend never saw a byte of it. That reroute is the whole story, and the satisfying part is that the "better" version contains conspicuously _less_ code than the one it replaced. ## The pipeline, end to end Recording happens in the renderer. `ScreenRecordingService.ts` creates a `MediaRecorder` over the capture stream with `{ mimeType: 'video/webm;codecs=vp9,opus', videoBitsPerSecond: 1_000_000 }` and starts it with a one-second timeslice (`CHUNK_INTERVAL_MS = 1000`). Every `ondataavailable` blob is shipped over IPC to the main process, where the `recording:save-chunk` handler appends it to a file on disk with `fs.promises.appendFile`. Worth flagging early, because it's a tempting thing to misread: that one-second cadence is the _disk-write_ rhythm, not an upload chunk size. By the time the upload starts, there's a single finished WebM file sitting on disk. What differs between the two releases is only what happens to that finished file. ## The before: everything through the backend (v1.8.11) The v1.8.11 upload path, in `CloudUploader.processRecording()`, is the obvious design and a completely reasonable first version: 1. `POST /api/geekbye/recordings` to create a backend record with the recording's metadata. 2. Read the entire video into memory and POST it to the backend as multipart — `const fileBuffer = fs.readFileSync(filePath)` — hitting `POST /api/geekbye/recordings/${id}/upload`. The backend then uploads that file to Drive and returns a `driveUrl`. 3. `POST …/process` to send the transcript for AI analysis. The load-bearing detail is step 2: **the client loads the whole recording into memory and every byte of it transits GeekBye's own servers on the way to Drive.** For a short clip that's fine. For a long screen recording it's three problems at once — memory pressure on the client from `readFileSync`, bandwidth and relay cost on the backend for a file it doesn't even keep, and a single monolithic POST that has to _restart from zero_ if the network hiccups halfway through. It's also worth being honest that this "before" wasn't some pristine original design. The v1.8.11 feature squashed together a within-week pivot: an early cut uploaded to Cloudflare R2 via pre-signed URLs and converted WebM to MP4 with a bundled `ffmpeg-static`; that was replaced by the backend-mediated Drive flow; and then the ffmpeg conversion was ripped out entirely in favor of uploading the WebM as-is (the commit notes it lowered the bitrate and produced roughly four-times-smaller files — an inline estimate, not a benchmark). So even the "before" had already learned to delete a native dependency once. The next release would delete the proxy too. ## The after: straight to Drive (v1.8.13) v1.8.13 rewrites `CloudUploader.processRecording()` around one sentence from the commit that shipped it: upload directly to Drive, _"keeping backend for metadata record + AI transcript analysis only."_ The new path: 1. `POST /api/geekbye/recordings` — **metadata only** (title, duration, file size, `format: 'webm'`). The comment in the code is blunt: `// Create backend record (metadata only — no file upload)`. 2. Upload the video **directly to Google Drive**, skipping the backend middleman entirely. 3. `PATCH /api/geekbye/recordings/${backendRecordingId}` with the resulting `{ driveUrl, driveFolderId }` — the backend record is _told_ where the file ended up. 4. Transcript still goes to `…/process` for analysis. The backend went from being _in_ the file's path to being _told about_ the file after the fact. It holds a row and a pointer; the bytes live only on the user's disk and in the user's Drive. ### Becoming a real Drive client For the client to talk to Drive directly, it has to be an actual Google Drive API client, and v1.8.13 adds the machinery for that: a new `DriveService` (the Drive client), a new `DriveAuthRepository` backed by a new `drive_auth` SQLite table, and the `googleapis` SDK as a dependency. The authorization is a clean capability hand-off. The backend's app-config endpoint relays Google OAuth **client credentials** — a `clientId` and `clientSecret` — down to the app, which `CloudUploader` reads and passes to `driveService.initialize(...)`. From there the desktop app runs the whole OAuth flow itself: it stands up a temporary `http` server on a random `127.0.0.1` port, opens the consent screen in the system browser with `shell.openExternal`, catches the redirect on a `/callback`, and exchanges the code for tokens with `google-auth-library`. Those tokens are saved locally — `saveDriveAuth(access_token, refresh_token, expiry_date)` into `drive_auth` — with the `drive.file` scope, which lets the app manage only the files it creates. Refresh is local too: an `oauth2Client.on('tokens', …)` listener writes refreshed tokens straight back to the table. The backend hands over the capability once and then stays out of the loop. ### The "resumable" part, honestly Here's the detail I most want to be straight about, because it's exactly the kind of thing a changelog rounds off. The release note says "resumable uploads," and that's true from where the user stands. But GeekBye did **not** implement the resumable protocol. There is no chunk-size constant, no byte-offset tracker, and no `Content-Range` / `308 Resume Incomplete` handling anywhere in GeekBye's source. Grep for them and you get nothing — they live inside the `googleapis` SDK. What GeekBye's own code does is this: ```js const fileStats = statSync(videoFilePath) const videoStream = createReadStream(videoFilePath) const videoRes = await this.drive.files.create( { requestBody: { name: `${title}.webm`, parents: [folderId] }, media: { mimeType: 'video/webm', body: videoStream }, fields: 'id', }, { onUploadProgress: (evt) => { const percent = Math.round((evt.bytesRead / fileStats.size) * 100) onProgress(percent) }, }, ) ``` Two choices do all the work. First, `media.body` is a **stream** (`createReadStream`), not a buffer — and passing a stream is precisely what makes `googleapis` negotiate a resumable session on your behalf instead of doing one shot. Second, progress is read off the SDK's `onUploadProgress` callback against `fileStats.size`. That's the entire "resumable upload" implementation at the application level: stream in, progress out. The hard protocol — open a session, upload in chunks, resume from an offset after a drop — is the SDK's job, and the correct engineering decision was to let it do that job rather than reinvent it. Which reframes the whole release. The reliability improvement didn't come from writing an upload engine. It came from _deleting_ the proxy that sat in front of one — and, earlier the same week, deleting the ffmpeg step in front of _that_. Less code, more resilience, because the resilient thing was a well-worn library the moment you stopped hand-feeding it a buffer. ## The bits that are genuinely GeekBye's Handing the transfer to the SDK doesn't mean there was nothing left to build. Two pieces of real application logic surround it. **An orphaned-folder rollback.** Each recording gets its own Drive subfolder (created with `mimeType: 'application/vnd.google-apps.folder'`), and the transcript lands there as a second file next to the video. If the upload throws, the `catch` deletes that folder — `this.drive.files.delete({ fileId: folderId })`, logged as `Rolled back orphaned Drive folder` — so a failed upload doesn't leave a trail of empty folders in your Drive. Creating a container before you know the operation will succeed is a small liability; making the failure path clean it up is the fix. **Lifecycle around a singleton.** `DriveService` is a singleton holding an in-memory OAuth client, and that in-memory client doesn't survive an app restart even though the tokens do (they're in the database). So `CloudUploader` re-initializes `DriveService` from the stored tokens _before every upload_, which means a recording made right after relaunch still uploads without asking you to reconnect. And the review pass turned the `drive-connect` handler into fire-and-forget, because a blocking connect call was stalling the renderer's status polling — the UI couldn't show "connecting…" if the connect call held the thread. Those two — re-init-from-storage and non-blocking connect — are the direct path's real edge cases, and notably neither of them is about upload chunking. When you delegate the hard part, the bugs you're left with are lifecycle bugs. ## Three things this release taught us 1. **Ask where the bytes flow, not just whether it works.** The v1.8.11 proxy worked. But routing every recording through your own servers costs you bandwidth, memory, and restartability for a file you don't keep. Redrawing the data path so the client talks to the destination directly is often the whole optimization. 2. **The best resumable upload is the one you didn't write.** Passing a stream to a mature SDK bought a full resumable session for two lines of code. The instinct to hand-roll chunking and offset math would have produced more code and less reliability. 3. **When you delegate the core, you inherit lifecycle, not algorithms.** The direct client's real bugs were "re-init the singleton after restart" and "don't block the connect call," not byte offsets. That's a good trade — lifecycle bugs are visible and local; protocol bugs are neither. For the previous chapter in the v1 story, [born and fixed in twelve minutes](/blog/born-and-fixed-in-twelve-minutes) (v1.8.10); for the next, [telling a call from an open app](/blog/telling-a-call-from-an-open-app) (v1.8.15–v1.8.19); and for the whole arc, [the anatomy of shipping software to perfection](/blog/anatomy-of-perfection).