Most "AI-powered" side projects follow the same arc: a slick demo, a hardcoded API key, one shared database, zero auth boundaries — and it falls apart the moment a second user shows up. I wanted to build something different: a real, production-shaped application, designed the way a security engineer would design it before a single line of code got written.
That's how Personal Gemini Journal came together — a private, AI-powered diary and brainstorming partner where every architectural decision starts with "what happens if someone tries to abuse this?" instead of "does this work in the demo?"
What It Does
Personal Gemini Journal isn't just another chatbot wrapper. It's a secure, fully isolated journaling space where you sign in, have real multi-turn conversations with Gemini to brainstorm or reflect, and have those conversations automatically summarized and saved — privately, to you.
The feature I'm most excited about is semantic search over your own past entries. Instead of scrolling through weeks of journal history, you can ask something like "When did I talk about feeling stuck on my job search?" and get back the actual entries that match — not by keyword, but by meaning.
The Stack
- Frontend: React 19 (Vite), Tailwind CSS v4, Lucide React
- Backend: Node.js + Express, written in TypeScript
-
AI: the
@google/genaiSDK — Gemini 2.5 Flash for conversation and summarization,text-embedding-004for vector embeddings - Auth & Data: Firebase Authentication (Google SSO) + Cloud Firestore
- Security: Google Cloud Secret Manager for API key protection, Zod for payload validation, and Firestore's native Vector Search
Four Features, Four Threat Models
Secure Authentication. The frontend never hands the backend a user ID and asks it to be trusted — it sends a Firebase ID token (JWT), and a custom Express middleware verifies that token server-side via the Firebase Admin SDK before deriving identity. The user is always who the token says they are, never who the request body claims they are.
Multi-Turn AI Journaling. Conversation history lives in Firestore, maintained by the backend — not the client — so context can't be tampered with mid-conversation. Every call to Gemini is proxied through the server; the API key never touches the browser.
Semantic Search via RAG. When an entry is saved, the backend asks Gemini 2.5 Flash for a concise summary, then passes that summary to text-embedding-004 to generate a vector embedding. A search query gets embedded the same way and matched against those vectors using Firestore's native findNearest with cosine distance — real retrieval-augmented search, not string matching.
Strict Data Isolation. This is the part I care about most. Data lives under users/{userId}/conversations/{conversationId}, and isolation is enforced in two independent layers: the Express backend hardcodes every query — including vector search — to the authenticated user's ID, and Firestore Security Rules separately enforce request.auth.uid == userId. Even if the backend were somehow bypassed, cross-user data leakage (IDOR) isn't just unlikely — it's structurally prevented.
The Security-First Angle
The part of this build that made it feel genuinely different from a typical hackathon project was the process, not just the output:
- No client-side secrets, ever. The Gemini API key is pulled from GCP Secret Manager at server startup and cached in memory — never bundled into the Vite build, never logged, never returned in a response.
- Validated input everywhere. Every API route runs through Zod schemas enforcing max lengths and rejecting malformed payloads, which matters more than it sounds — an unvalidated free-text endpoint hitting a billed LLM API is an open door to a cost-exhaustion attack.
-
Least privilege by default. The backend's service account gets
roles/secretmanager.secretAccessorscoped to exactly the Gemini API key it needs — not broad project access "to be safe."
None of this shows up in a demo GIF. All of it is the difference between a toy and something you could actually ship.
Why This, Why Now
AI-assisted coding tools can generate a working app in minutes — but "working" and "production-ready" are different bars. The interesting engineering problem isn't getting Gemini to respond to a prompt; it's making sure the system around that response holds up under real users, real attackers, and real scale. Deploying this kind of security-conscious, isolated architecture on Cloud Run is what makes the difference between a weekend demo and something you'd actually trust with your own data.
Built with Gemini 2.5 Flash, Firebase, Firestore, and Cloud Run.
#AccelerateAIwithCloudRun
Top comments (0)