Recently, I made my first full-stack application. Previously, I’ve delved into AI/ML and its applications, but I realized I’d never actually implemented a full-stack project where I build the frontend, build the backend, and then get them both to talk. I also wanted to incorporate some core DSA concepts—and of course, a little touch of AI too. That’s when I stumbled upon the idea of making LexiLearn AI, an adaptive, intelligent flashcard platform. It’s not just a card viewer; it’s a smart study tool designed to maximize learning efficiency.
This definitely was ambitious for my first time, but what am I without my Icarus-like tendencies? I would rather burn than never fly.
To start off, I first understood that the backend and frontend are two completely separate entities, and they need to be built like that too. They run on two separate ports, and we need to use something called
CORS (Cross-Origin Resource Sharing) to allow sharing between them.
Origin basically consists of a protocol + domain + port.
For example:
Frontend: http://localhost:3000
Backend: http://localhost:5000
The browser sees these as different origins and blocks requests for security reasons. CORS fixes that by explicitly allowing cross-origin requests.
Building the Backend
Inside the app folder, I organized everything into subfolders.
Core Folder
This contains all the foundational files.
1. config.py: Manages app settings by loading values from .env files—keeping secrets out of code and making deployment easy.
2. database.py: Sets up the connection to the database using SQLAlchemy, so I never have to write raw SQL.
3. deps.py: Think of this like a hotel concierge—it hands out a fresh database session (your room key) and checks your JWT token (your VIP badge) before letting you into protected routes.
Models, Routers, Schemas, and Services
Models: Python classes that map directly to database tables (like User, Deck, Card), with relationships like “one user has many decks.”
Schemas: Pydantic models that define what data looks like when it enters or leaves the API—auto-validating input and hiding sensitive fields.
Routers: Your actual API endpoints (POST /decks, GET /cards). They’re thin: just receive requests, call services, and return responses.
Services: Where the real logic lives—creating users, saving cards, running algorithms—without any knowledge of HTTP or FastAPI.
All together, it’s like a restaurant:
Routers are the waiters—they take your order and bring back your food.
Schemas are the menu—they define what you can order and what info the kitchen needs.
Models are the pantry—they store the ingredients (data) and how to retrieve them.
Services are the chefs—they do the real cooking (business logic).
Together, they make the whole system work.
Now, in the services, we implement our core logic.
I learned that for this, I’d have to build a CRUD application—Create, Read, Update, Delete. It’s as straightforward as it sounds: for every main thing in your app (like decks or cards), you need to support all four operations. In LexiLearn, that means adding a flashcard, viewing your deck, renaming it, or deleting it.
Where Do Data Structures and Algorithms Fit In?
I’ve always been puzzled about where DSA shows up in real life—beyond LeetCode’s artificial problems. So I decided to put it to use.
The heart of LexiLearn is spaced repetition: a method where you review flashcards just before you’re likely to forget them. Hard cards appear more often; easy ones fade into the background.
To make this work, I needed to always serve the most urgent card—the one with the earliest next_review_date. Instead of sorting the whole list every time, I used a min-heap (a type of priority queue). It keeps the most overdue card at the top, and popping it is fast.
When you answer a card, the system adjusts your schedule: get it right, and you won’t see it for a while; get it wrong, and it comes back quickly. The timing adapts to your memory instead of a fixed rule.
At the core of it all, I wanted to implement some AI in it too. It's impractical to expect the exact answer down to the t, And that’s how real learning works too: if you understand a concept, you can explain it differently.
Most flashcard apps fail here—they demand exact string matches.
So I used the GROQ API to do two things:
- Verify answers by checking semantic meaning, not just keywords.
- Generate hints on demand—subtle nudges that guide you without giving away the answer. This makes LexiLearn feel less like a quiz and more like a conversation with a smart tutor.
The Best Part: Testing with Swagger UI
After building everything, I needed to test whether it actually worked. To run the backend server, I used:
python -m uvicorn app.main:app --reload
It had bugs at first—but then I discovered Swagger UI.
Just go to http://localhost:5000/docs, and you get a live, interactive API console. I could send sample requests, inspect responses, and confirm things were working—like getting a 401 when I forgot my token, or a 200 when a card was reviewed successfully.
That was so interesting to me. Suddenly, my API wasn’t just code—it was alive, testable, and real.
## Crafting the Frontend: My First Journey with React
Then i moved on to making the frontend. I chose to build it with React, and my first "aha!" moment was understanding its core philosophy. You don't command the screen to change; you tell React what the screen should look like based on your data (your state).
React then handles the "how." When your state changes, it creates a new "blueprint" of your UI in a lightweight copy called the Virtual DOM. It compares this new blueprint to the old one, calculates the absolute minimum set of changes required, and then surgically updates the real screen. This is what makes it feel so fast and modern.
The "Central Nervous System": React Context
I immediately hit a classic problem: how does my header know if a user is logged in? The messy solution is "prop drilling"—passing the login status down through ten levels of components. The professional solution I implemented was React Context. I created an AuthContext that acted as a global "radio station" for my app's login status. Any component could "tune in" using a simple useAuth() hook to get the user's token or the logout() function.
The "GPS": Multi-Page Navigation with React Router
To make the app feel fast, I built it as a Single Page Application (SPA) using the industry-standard react-router-dom. My App.jsx file became the application's "GPS," using a component to decide which page to show. The most powerful features were:
Protected Routes: I wrapped my main pages in a check. If a user didn't have a login token from the AuthContext, they were automatically redirected to the login page.
_
Dynamic Routes:_ I used paths like /decks/:deckId. This allowed a single component, , to handle the study session for any deck by reading the ID from the URL.
The "Head Waiter": Centralizing API Calls
Instead of scattering axios calls all over my components (a maintenance nightmare), I created a dedicated "IT Department" for my app: the apiService.js file. This file centralizes every function that communicates with my backend. If I ever need to change an endpoint URL, I only have to change it in this one file. This is a professional pattern called abstraction that kept my component code incredibly clean.
The Fun Stuff: A Beautiful and Interactive UI
Finally, I wanted the app to be fun to use. In my index.css, I defined a vibrant, "Cosmic Lavender" theme with CSS Variables and created a slowly shifting, animated gradient for the background to make it feel alive.
I used to think frontend was just HTML, and that anyone could do it. But I’ve realized it takes a lot more than code to make an application feel right. A lot of thought has to go into the design — not just how it looks, but how it feels to use.
I’m starting to see that building something beautiful and intuitive isn’t accidental. It’s intentional. That’s why I want to dive deeper into UI/UX and design thinking — to truly understand how to make my websites look and feel exactly how I imagine them.
All in all, building a full-stack application was eye-opening. I didn’t have much exposure to this before, and now I can see that frontend and backend aren’t just “HTML” or “Python.” There are so many moving parts — state, APIs, databases, styling, routing — and bringing them all together, making everything work exactly the way you want, is its own kind of art.
And honestly? That’s what makes it so rewarding.
Top comments (4)
This was such a great read, Advithiya — love how you broke down both the backend and frontend sides with real lessons from your first build. 👏
I could totally relate to that “everything finally works together” moment — it’s the best feeling for indie makers building AI tools!
The LexiLearn idea is brilliant — mixing spaced repetition and semantic AI checks shows real insight into user learning. Keep sharing your journey — this kind of content inspires a lot of us working on small AI-driven projects too. 🚀
Good to know Shemith :)
Your analogies routers as waiters, schemas as menus, services as chefs make complex backend structure crystal clear. Genius!
Glad to hear, thank you!