Building My First SaaS in Go: The DsEasy Journey

How a scrappy Python app evolved into my first attempt at a Go-powered SaaS for exam generation

Anas

  ·  8 min read

Building a SaaS application is never a straight line from idea to execution. My journey with DsEasy has been a masterclass in learning Go, unlearning bad patterns, and discovering that sometimes the simplest approach is the best approach. What started as a favor for a professor has evolved into a full-featured exam generation platform—but not without some spectacular mistakes along the way.

The Genesis: Solving a Real Problem #

The story of DsEasy begins not with grand entrepreneurial ambitions, but with a professor’s frustration. My dear friend and professor was drowning in the tedious process of creating exams for his students. He needed variety, he needed efficiency, and he needed it yesterday.

His solution? Hire me to build a local Python application using Tkinter where he could:

  • Maintain a database of exercises organized by lessons
  • Select specific lessons for an exam
  • Input the number of exercises per exam
  • Click generate and receive a ready-made PDF

The result was functional but far from beautiful—a classic case of “it works, but nobody wants to look at it.” I used LaTeX for the actual exam generation, storing templates and exercises in .tex files. Despite its ugliness, the professor was satisfied, paid me for my work, and graciously allowed me to open-source it as a portfolio piece.

That ugly little app became DsGen, and while it solved one professor’s problem, it planted a seed for something much bigger.

The Pivot: What If This Was a Service? #

A year later, while browsing my GitHub and contemplating my next project, I stumbled upon that old Python iteration. A thought struck me: what if this wasn’t just a desktop app for one professor? What if it was a service?

The vision crystallized quickly:

  • A central database of exercises and questions
  • Subscription-based access for professors
  • Community contributions where educators could add their own exercises
  • Infinite variety in generated exams through collaborative content

I was all in. At the time, I was deep into the Go + Templ + HTMX + Tailwind stack, so the technology choice was obvious. DsEasy would be built with modern Go tooling.

Version 0: The Framework Trap #

Choosing Echo: Following the Crowd #

Fresh from my first year in the workforce at a startup obsessed with frameworks, I approached DsEasy with the same mentality. Like any newcomer to Go, I googled “best web framework for Go” and was presented with a buffet of options, each promising different features and capabilities.

I chose Echo based on little more than popularity and feature lists—a decision that would later haunt me.

Database Decisions: Raw SQL and SQLite #

In a rare moment of restraint, I decided against ORMs. Instead, I wrote all my SQL queries by hand—no migrations, no abstractions, just raw SQL against SQLite. While this choice would prove wise in principle, my execution left much to be desired.

SQLite was perfect for the lightweight nature of the application, but my approach to schema management was, charitably speaking, chaotic.

Architecture: N-Tier in Go Land #

Here’s where things went spectacularly wrong. Armed with fresh knowledge of N-Tier Architecture and SOLID principles from my NestJS experience at work, I decided to impose this structure onto my Go application.

Every feature was split into three layers:

  • Repository (data access)
  • Service (business logic)
  • Controller (HTTP handling)

I felt incredibly smart creating this “professional” architecture. I was following best practices! I was writing clean code! I was… completely missing the point of Go.

The Rude Awakening #

The application worked, but something felt fundamentally wrong. Then I discovered 100 Go Mistakes and How to Avoid Them by Teiva Harsanyi, and experienced what I can only describe as architectural whiplash.

Without exaggeration, I had made every single mistake mentioned in that book. My code was the antithesis of idiomatic Go:

  • Over-abstracted interfaces where simple functions would suffice
  • Unnecessary layers that obscured rather than clarified
  • Framework patterns fighting against Go’s simplicity
  • Complex dependency injection where direct calls made more sense

The realization was humbling and decisive: the project was beyond salvation. A complete rewrite was necessary.

Version 0.5: Embracing Go’s Philosophy #

Out with Frameworks, In with the Standard Library #

The concept for DsEasy v0.5 was beautifully simple: embrace the standard library as much as possible. I kept the general stack (Go + Templ + HTMX + Tailwind) but replaced Echo with the http standard library coupled with gorilla/mux for routing.

Using Go Blueprint to generate the initial project structure, I started fresh with a much more Go-idiomatic approach.

SQLc: The Best of Both Worlds #

While I maintained SQLite as my database, I made two crucial upgrades: SQLc instead of manually embedded SQL queries, and gomigrate to properly handle database migrations. This gave me:

  • Type-safe queries (like ORMs)
  • Without the complexity and cryptic joins
  • Clear separation between SQL and Go code
  • Compile-time verification of queries
  • Proper migration management (finally!)

I’ve written more extensively about my SQLc experience here, but it represents one of the best decisions in the entire project.

Template Evolution: Typst Over LaTeX #

Another significant upgrade was switching from LaTeX to Typst for exam generation. Typst offered:

  • Simpler syntax
  • Better error messages
  • Faster compilation
  • More intuitive template system

The PocketBase Experiment #

For file storage and authentication, I integrated PocketBase to handle:

  • Typst template files
  • Exercise storage
  • User authentication
  • Basic admin functionality

This worked well for an MVP and allowed me to focus on core features rather than rebuilding authentication from scratch.

The Frontend Split: HTMX to React #

After building an initial version where exams could be generated successfully, I ran into several issues with HTMX that made me reconsider the frontend approach. While HTMX is powerful for simple interactions, the complexity of exam generation workflows demanded more sophisticated state management.

The decision was pragmatic: split the frontend and backend, creating a proper React frontend that communicates with the Go backend via API.

Current State and Future Plans #

Today, DsEasy consists of:

  • Backend: Go with standard library + gorilla/mux
  • Database: SQLite with SQLc for type-safe queries
  • Frontend: React application for exam generation and exercise management
  • File Storage: PocketBase for templates and user data
  • Authentication: Currently in development

The projected timeline is 1-2 weeks to ship the first production version—a testament to how much cleaner and more maintainable the codebase has become.

Lessons Learned: The Philosophy of Simplicity #

Framework Fatigue is Real #

My first version suffered from framework fatigue—the belief that every problem needs a framework solution. Go’s standard library is remarkably complete, and adding frameworks often introduces more complexity than value.

Architecture Shouldn’t Fight the Language #

Trying to impose N-Tier architecture (and how it was implemented in NestJS) onto Go was like trying to wear a suit three sizes too big. The language has opinions about simplicity and directness; fighting those opinions leads to awkward, unnatural code.

Start Simple, Add Complexity When Needed #

Version 0.5’s success came from starting with the simplest possible implementation and adding complexity only when justified. This approach led to cleaner code and faster development cycles.

The Right Tool for the Right Job #

SQLc hit the sweet spot between raw SQL complexity and ORM overhead. Sometimes the best solution isn’t at either extreme but somewhere in the middle.

User Experience Drives Technical Decisions #

The decision to split frontend and backend wasn’t driven by architectural purity but by user experience requirements. Sometimes the right technical decision is the one that serves users best, even if it increases system complexity.

The Broader Impact: From Learning Project to Business #

What started as a learning exercise has evolved into something with real business potential. The original professor’s problem wasn’t unique—educators everywhere struggle with exam creation, and the market for educational technology continues to grow.

DsEasy represents more than just a technical project; it’s a case study in:

  • Problem validation through direct user feedback
  • Iterative development and learning from mistakes
  • Technology choices that serve business goals
  • The importance of simplicity in software architecture

Looking Forward: Building for Scale #

As DsEasy approaches its first production release, the lessons learned from this journey inform every decision:

Simplicity First: Every feature addition is evaluated against Go’s philosophy of simplicity.

User-Driven Development: Technical decisions are made in service of user experience, not architectural elegance.

Iterative Improvement: Rather than building everything at once, features are added based on user feedback and real needs.

Maintainable Codebase: The architecture prioritizes long-term maintainability over short-term feature velocity.

The Meta-Lesson: Failure as Education #

Perhaps the most valuable aspect of this journey has been learning to recognize and recover from architectural mistakes. The first version of DsEasy wasn’t a failure—it was education. The patterns that seemed professional and sophisticated were actually impediments to building software that aligned with Go’s design philosophy.

This experience has fundamentally changed how I approach new projects, emphasizing:

  • Language idioms over universal patterns
  • Standard libraries over external frameworks
  • User problems over architectural aesthetics
  • Iterative complexity over upfront design

Conclusion: The Continuous Journey #

DsEasy’s development continues, but the journey from ugly Python app to production-ready SaaS has already provided invaluable lessons. The path wasn’t straight, the mistakes were numerous, but each iteration brought deeper understanding of both the problem space and the tools used to solve it.

Building software is ultimately about solving human problems with elegant solutions. Sometimes the most elegant solution is also the simplest one—a lesson that took building DsEasy twice to fully appreciate.

The next phase focuses on user onboarding, payment integration, and scaling the database of exercises through community contributions. But those challenges will be approached with the hard-won wisdom of knowing when to add complexity and when to embrace simplicity.

After all, the best code is often the code you don’t have to write.