Case Study

devGaido

devGaido hero image
How It All Began

Introduction

When Jim Medlock, Co-Founder of Chingu, asked me if I wanted to join his team to develop an application that would try to streamline the learning experience of new and experienced web developers alike, accepting was a no-brainer. Not having much remote working experience under my belt, I felt a bit nervous, but the prospect of such a great learning opportunity was just too good to pass up.

devGaido is a platform that helps (aspiring) web developers efficiently locate learning resources and simplifies studying by combining "lessons" into "paths" that follow different learning goals.

Lessons and paths can each be bookmarked, progress towards completion is tracked and paths tangential to your completed lessons are recommended automatically.

first design of devgaido
redesigned devgaido styleguide
Technologies used

Tech Stack

icons of our tech stack
MongoDB, React, Node.js, Redux, Stylus, Auth0, Docker, NGINX

The tried and true MERN stack ( MongoDB, Express, React, Node.js) and Redux were obvious choices because of their ease of use and reliability. Stylus was chosen so we could use the much terser indented syntax, but still paste in snippets in regular syntax.

As we didn't want to risk leaking private data, we let Auth0 handle authentication and only saved the user id for identification.

Docker was used to orchestrate the server deployment and NGINX was utilized here as a reverse proxy to lessen the load on the server and to allow it to scale more easily in the future.

Why devGaido?

Purpose and Goal

We built devGaido to provide a guided learning experience for people wanting to learn web development, especially those using the excellent P1xt Guides.

The problem we wanted to address was that the guides by their nature are text-only which paired with their flexible curriculum structure made it somewhat hard to follow them or track your progress.

devgaido's library showing the p1xt guides
The Obstacles Along The Way

Challenges

As I can't possibly cover all the hurdles we had to overcome, and since frankly no one would actually want to read about all of them - I tried to boil this section down to the most interesting pitfalls we encountered.

Problem No. 1

CSS Management

piechart showing coming up with CSS class names takes too much time
Source: https://css.christmas/2019/22

The Challenge

While we tried our best to adhere to BEM, coming up with CSS class names for a new Component was still pretty time-consuming. Add to that the time spent constantly flipping back and forth between the Component's code and its CSS and it becomes a major problem.

The Solution

Even though the idea of functional/atomic CSS frameworks like Tachyons and Tailwind was pretty novel back then, we opted to use this approach to compose the brunt of our CSS directly in our code.

To be able to tweak it to our needs in regards to naming and features I was tasked with creating our own version of such an atomic CSS framework (aptly named "Atomiku").

Problem No. 2

Bloated CSS

screenshot of purifycss

Challenge #1

Using Atomiku made creating new Components and their style much easier and - more importantly - much faster. But it came with a huge drawback: As we had created utility CSS classes for all kinds of scenarios this meant that our production CSS file was pretty bloated, even though we only used a fraction of the classes in our code.

Challenge #2

To get rid of the unnecessary CSS, we decided to use PurifyCSS as a final build step. This worked great at first glance but when we inspected the resulting CSS we noticed that classes with numbers in them like "width-50" did, in fact, NOT get removed at all.

The Solution

Luckily, the fix was pretty easy to implement as all I had to do was fork PurifyCSS, edit some regex (and adjust the tests for good measure) and add that fork to devGaido. From then on, only the absolutely needed CSS classes remained in the production CSS.

Problem No. 3

Function Components

screenshot of the medium article
Source: https://medium.com

The Challenge

Driven by the promise of (future) performance benefits and the elegance of arrow functions, we tried to use function components wherever possible. This however lead to a pretty big problem: We couldn't use local state and had no access to lifecycle functions. Although this was fine for the majority of the codebase, it hurt a lot in the places where it wasn't.

The Solution

In absence of React Hooks, which had yet to be invented, I didn't want to admit defeat and resort to using a Class component. So, after a lot of trial and error I came up with the following approach:

  • Create a wrapper HOC that exposes state, setState(), and the lifecycle hooks
  • Invoke the function component as a function inside the wrapper to get the JSX but not create another component
  • Overwrite the static name() getter function to return the original component's name

For more details, check out the Medium story I wrote about this.

Problem No. 4

Lesson images

placeholder images used everywhere in the app

The Challenge

Throughout the app, we wanted to show images relating to the paths and lessons to spice up the visuals, but with such a large number of lessons, it would have taken ages to find all the necessary assets. Furthermore, that meant we would have needed to find and upload appropriate images when adding more lessons in the future.

The Solution

Seeing that devGaido exclusively linked to external resources for its learning materials, the idea of just using the URLs of the lesson resources and letting the node server take screenshots was born. The resulting images weren't as awesome as handpicked ones obviously, but it was a great time saver and worked fine for our use case.

Problem No. 5

Server load

load balancing
Source: https://nginx.com

The Challenge

To keep the costs low for our MVP version, our production server was running on a "best-bang-for-your-buck" VPS, which was, realistically speaking, still pretty low-performance. This fact was backed up by our load tests which suggested that our Node.js server could only handle a moderate number of concurrent requests before breaking down. We assumed this could potentially lead to severe problems in the future if left unaddressed.

The Solution

As a large portion of our server requests was retrieving the lesson and path images, the first step was to incorporate Cloudflare as a CDN to serve our static assets cached. Not only did this lessen the load immensely but it also let us drop Let's Encrypt SSL and leverage Cloudflare for this as well.

The next step was putting NGINX in front of the Node.js server as a reverse proxy. On the one hand, this worked as a "buffer" for our server, on the other hand, it enabled load balancing capabilities across multiple Node instances.

As the final step, I proposed sending over the complete curriculum data (lessons and paths) with the initial page request via server-side Redux store hydration. We already hydrated the initial Redux store with user data and authentication details anyway, so the idea wasn't too far-fetched.

While further reducing server load, this method also increased the perceived speed of the app, since there was no loading except for the initial page load.

Generating static HTML would have probably been the cleaner solution but it would have necessitated rewriting large parts of our custom server-side rendering code, so we decided to take the more time-efficient route here.

In the end, we were able to cut the number of requests our server receives by 90%, made our server sturdier, and also enabled scaling capabilities via load balancing.

Knowledge Gained

Lessons Learned

word cloud around communication
Source: https://pros-blog.padi.com/

The main takeaway for me from this project is:

Communication is key.

It doesn't matter if it's between team members or with your users - the way you communicate can make or break a project.

We had numerous, sometimes heated discussions about various aspects of devGaido - direction, UI/UX, app architecture, and countless other details - more than enough potential for controversy. But even in the most passionate of debates, we always made sure to be respectful and willing to listen and give careful consideration to opposing views.

That doesn't mean that we compromised on the product just for sake of "getting along" - sharing opinions and arguing for one's beliefs was equally as important - but we took great care to keep it all professional.

It's not about winning a popularity contest but about trying to create an environment where everyone feels heard and where ideas can blossom.

That extended to communication with our userbase as well:
Though we couldn't possibly fix every issue instantly or answer every question thoroughly on the spot, we always made sure to respond in some way - no one likes to feel being ignored.

Conclusion

Closing Thoughts

I am grateful to have been part of this project and to be able to work with these great people. The amount and quality of things I learned on this journey are immeasurable - I thank everyone involved for this opportunity.