DEV Community

Michael Abaniwo
Michael Abaniwo

Posted on

Quizzo - The Smart Teaching Assistant

Quizzo Deployed link

Comprehensive Build Guide & Interview Prep

This document serves as a complete walkthrough of how the Quizzo Teacher Assistant application was built. It is designed to help you understand the architecture, explain your technical decisions during an interview, and confidently discuss how all the pieces fit together.


1. High-Level Architecture Overview

Quizzo is a Full-Stack AI Application separated into two distinct repositories (or folders):

  1. Frontend (Client): Built with Next.js (React) and TypeScript. It handles the user interface, authentication state, and the chat window.
  2. Backend (Server): Built with Node.js, Express, and TypeScript. It handles user authentication, session management, and securely communicates with the Google Gemini AI.

Why did we separate the frontend and backend?

  • Security: We must never expose our GEMINI_API_KEY to the browser. By keeping an Express backend, the frontend simply asks the backend for a chat response, and the backend securely talks to Google.
  • Scalability: The backend can handle complex AI logic, database connections, and long-running tasks without slowing down the user's browser.

2. Step-by-Step Implementation

Phase 1: The Backend Foundation (Node & Express)

We started by creating a REST API using Express and TypeScript.

  • In-Memory Store: To keep things lightweight for the initial build, we used an in-memory array (memoryStore.ts) to store Users and Chat Sessions. (Interview Tip: Mention that in a real production app, this would be replaced by a database like PostgreSQL or MongoDB).
  • Authentication:
    • We built /api/auth/register and /api/auth/login.
    • We used bcrypt to securely hash user passwords before saving them.
    • We used jsonwebtoken (JWT) to generate a secure token when a user logs in.
  • Auth Middleware: We created auth.middleware.ts to protect our chat routes. This intercepts requests, checks if the user has a valid JWT in their Authorization header, and rejects unauthorized access.

Phase 2: Integrating Google Gemini AI

This is the "brain" of the application. We used the @google/generative-ai SDK.

  • Configuration (gemini.ts): We initialized the AI client using our secret GEMINI_API_KEY. We also implemented a Fallback Model Strategy, meaning if our primary model (gemini-3.5-flash) goes down or hits a rate limit, the system automatically tries secondary models to ensure the app stays online.
  • Service Layer (gemini.service.ts): We created a handleChat function that takes the user's message and their past conversation history, sends it to Gemini, and returns the response.
  • Function Calling / Tools: This is the most advanced part of the AI integration. We didn't just build a chatbot; we gave the AI "Tools" it can execute.
    • generate_quiz: The AI can output a structured quiz based on a topic.
    • grade_response: The AI can compare a student's answer against a rubric and grade it.
    • summarize_performance: The AI can aggregate grades and provide a class summary.
    • How it works: The AI detects when a user asks for a quiz. Instead of just replying with text, it tells our backend "Execute the generate_quiz tool". Our backend runs the tool, hands the data back to the AI, and the AI presents it to the user.

Phase 3: The Frontend Experience (Next.js)

We built the user interface using Next.js.

  • AuthContext: We used React's Context API to manage global state. When a user logs in, AuthContext saves their JWT token to localStorage so they stay logged in even if they refresh the page.
  • API Service (api.ts): We centralized all our backend fetch requests here. It dynamically reads process.env.NEXT_PUBLIC_API_URL so it knows whether to talk to localhost or the live deployed backend.
  • The UI: We built a clean Login and Register page, highlighting our tagline: "The Smart Teaching Assistant". We then built a dynamic chat interface that updates instantly when the user sends a message, handling loading states while waiting for the AI to respond.

Phase 4: Testing & Quality Assurance

Writing tests proves to interviewers that you care about code quality.

  • Jest & SWC: We set up a test suite using Jest. We specifically opted for @swc/jest because the default ts-jest runner had compatibility issues with our modern version of TypeScript. SWC is written in Rust and compiles tests incredibly fast.
  • Unit Tests: We wrote tests for auth.middleware.ts to ensure it correctly blocks bad tokens and allows good ones. We also tested our AI tool definitions (like grade_response.test.ts) to ensure they return the expected schemas.

Phase 5: Deployment

We deployed the app using two different specialized platforms:

  • Netlify (Frontend): Next.js works flawlessly on Netlify. We provided Netlify with our NEXT_PUBLIC_API_URL environment variable so it knows how to find our server.
  • Render (Backend): We deployed the Express server to Render because it is designed for long-running Node.js processes. We added our GEMINI_API_KEY and JWT_SECRET securely to Render's environment dashboard.
  • CORS Fix: We encountered a CORS error where the backend blocked the frontend. We fixed this in server.ts by explicitly allowing requests from our Netlify URL.

3. Common Interview Questions & How to Answer Them

Q: Why did you use Next.js instead of just standard React?

"Next.js provides an excellent developer experience out of the box. It gives us an intuitive file-based routing system, built-in optimization for fonts and images, and a clear path if we ever wanted to move some of our Express backend logic into Next.js Server Actions or API routes in the future."

Q: How did you secure the application?

"Security was a priority at multiple layers. First, passwords are never stored in plaintext; we salt and hash them using bcrypt. Second, we use stateless JWT authentication, passing tokens via headers. Finally, our Gemini API key is strictly kept on the server and is never exposed to the client bundle."

Q: How do the Gemini AI Tools/Functions work in your app?

"We implemented 'Function Calling'. We defined a schema of tools (like grade_response) and passed them to the Gemini model. When a teacher asks a specific question, the AI determines which tool to use, pauses its generation, returns a JSON object with arguments to our backend, our backend executes the javascript logic for that tool, and hands the result back to the AI to format a final answer."

Q: What would you do differently if you were to scale this to 100,000 users?

"Currently, we use an in-memory store for users and chat history. To scale, the immediate first step would be implementing a persistent database like PostgreSQL for relational user data, and perhaps Redis for fast session/chat history retrieval. I would also add rate-limiting to the backend to prevent abuse of our Gemini API quota."

Q: What was the hardest bug you faced and how did you solve it?

"When deploying, we hit an ERESOLVE npm error on Render because of a strict peer dependency conflict with an older version of ts-jest and our modern typescript version. I diagnosed it by reading the build logs, uninstalled the conflicting package since we had already migrated to the much faster @swc/jest runner, and pushed the clean package.json to trigger a successful build. We also navigated a CORS block by configuring our Express middleware to specifically whitelist our live Netlify domain."

Top comments (0)