Back to Home

Why I Built Elearner: A Platform Designed for Learning

Introduction

Today, I want to share one of my favorite side projects: Elearner, a platform designed specifically for learners.


Why Elearner

As a software engineer, learning is not optional. The field evolves rapidly, and staying relevant requires continuous learning, often at a fast pace. Your long term career growth depends on your ability to absorb new concepts and apply them effectively.

Engineers approach learning in different ways. Some prefer writing notes in physical notebooks. Others rely on digital tools such as Notion or Obsidian. And some engineers don't take notes at all, yet still manage to be highly skilled and efficient.

There isn't a single "correct" way to learn.

However, research in Cognitive Science and Educational Psychology has produced powerful learning methods that align with how the human brain processes information. Techniques such as Active Recall, Spaced Repetition, and note-taking approaches like the Feynman Technique or the Cornell Method are widely used because they leverage how memory actually works.

As someone who uses note-taking tools extensively for learning (especially Notion and Obsidian) I eventually realized something important: most note-taking tools are not truly optimized for learning.

Notion, for example, is extremely flexible. But flexibility doesn't automatically translate into an effective learning workflow. I personally use it for documentation, journaling, and general writing tasks, things I rarely need to revisit or revise.

Learning, however, is different.

Effective learning requires more than just storing notes. It involves:

  • Questioning your understanding
  • Testing yourself repeatedly
  • Revisiting concepts over spaced intervals
  • Practicing what you learn
  • Organizing both primary and supporting resources

In other words, learning is not just note-taking, it's a process. And most existing tools treat it as simple information storage rather than an active learning system.

That realization is what led me to build Elearner.

What Elearner Does

In Elearner, every learning effort is organized into a single entity called a "Learn"

A Learn acts as a container that groups all the activities related to a specific topic you are studying. Instead of scattering notes, bookmarks, exercises, and review material across different tools, everything lives in one structured place. The image below shows that you can have three different learns for different topics (Linux, Java, and Docker). Each learn has its own resources, separate from the others.

Screenshot showing multiple Learns (Linux, Java, Docker)

Each Learn contains the following components:

Notebooks

Notebooks are where you write and organize your notes for a given topic. They function as flexible note editors where you can capture ideas, explanations, and summaries while studying.

You can create as many notebooks as needed, without restrictions, allowing you to structure your learning however you prefer.


Resources

While learning, you constantly encounter valuable content (social media posts, articles, documentation pages, or YouTube videos) that support your understanding.

Most people deal with this by bookmarking links in their browser, saving them in a separate bookmarking app, or storing them inside a document in tools like Notion.

The problem is that these resources often get buried and forgotten.

In Elearner, you can attach these resources directly to the Learn they belong to. This keeps them visible and connected to the topic you're studying, rather than lost among unrelated bookmarks.


Flashcards

As a software engineer, understanding theory and foundational concepts is what separates experienced engineers from average developers.

Flashcards are a powerful tool for reinforcing theoretical knowledge through active recall, a learning technique widely studied in Cognitive Psychology.

Instead of passively rereading notes, flashcards force you to retrieve knowledge by answering questions about the concepts you've learned.

Elearner also schedules these flashcards for review using Spaced Repetition, a method that revisits information at increasing time intervals. This approach works with the brain's natural forgetting curve, improving long-term retention.


Practice Tasks

Flashcards help reinforce theoretical knowledge, but real learning requires applying that knowledge in practice.

Practice tasks allow you to define small exercises or challenges that test whether you can actually use what you've learned. Each task can include instructions and a clear expected outcome.

Like flashcards, these tasks can also reappear over time, encouraging repeated practice and deeper mastery.

Tech Stack

In this section, I'll share the technologies and tools I chose to build Elearner.

Since this is a side project, my main goal was to keep the tech stack as small as possible so I could iterate quickly. Because of my experience with React and Next.js, I decided to build Elearner as a full stack Next.js application.

This allowed me to avoid maintaining a separate backend service and remain entirely within the JavaScript ecosystem. As a result, I was able to move from idea to implementation much faster than I initially expected.

Below are the main technologies I used.

💡 Note

Some of the reasons mentioned here may become outdated by the time you read this. The JavaScript ecosystem evolves rapidly, and new tools often emerge that solve these problems more elegantly.

Next.js

Before building Elearner, most of my experience with Next.js involved building applications that rendered primarily on the client side. The backend services in those projects were implemented in other languages and maintained by separate teams.

With Elearner, I decided to experiment with server-side rendering and server actions, treating Next.js as a true full-stack framework. This turned out to be a worthwhile experiment for several reasons.

Better Developer Experience

Using server rendering significantly simplified the data-fetching workflow.

In previous projects, I relied on tools such as:

  • useEffect
  • data-fetching libraries like TanStack Query

These tools were necessary because the code ran entirely on the client.

With server components, the situation changes fundamentally: the code runs on the server, so data can be fetched directly where it is needed. This eliminated several layers of complexity:

  • no client-side data-fetching hooks
  • fewer synchronization issues
  • less reliance on external libraries

As a result, the codebase became significantly cleaner.


Simpler UI Updates After Mutations

In traditional client-rendered applications, mutations often require manual cache updates or refetching logic to keep the UI synchronized.

Libraries built around GraphQL can help automate this process. For example, tools like Urql Client automatically update cached data based on mutation responses.

However, in many situations developers still need to handle synchronization manually.

With Next.js server rendering, the process is much simpler. After performing a mutation, a call to router.refresh() re-renders the page and fetches fresh data from the server, keeping the UI consistent without complex cache management.


Shared Types Between Frontend and Backend

Because the frontend and backend logic live in the same codebase, there is no need to generate types for API responses.

Instead of maintaining separate type definitions for server responses, the frontend can directly use the same TypeScript types defined in the backend layer. This reduces duplication and eliminates a common source of type mismatches.


React

Elearner's user interface is built using React.

During this project, I experimented with several APIs introduced in React 19.

useOptimistic

The useOptimistic hook allows the UI to update optimistically before the server action completes. If the server request fails, React automatically rolls back the optimistic update.

This approach provides a much smoother user experience without requiring complex manual state management.


useActionState

For form handling, I typically rely on React Hook Form. However, for simple forms it can sometimes feel like unnecessary overhead.

React's useActionState hook simplifies this by allowing forms to directly interact with server actions and automatically track the action state. For straightforward forms, this results in significantly less boilerplate.


Supporting Tools

In addition to the core stack, I used several supporting tools:

  • Chakra UI: a component library and design system that accelerated UI development
  • NextAuth.js: authentication and session management
  • Prisma: database ORM with strong TypeScript integration
  • Vercel: hosting and deployment platform optimized for Next.js applications