How a Solo Project for Earnware Forced Me to Master These 3 Fundamentals

June 28, 2025 (1y ago)

If you'd told me that a single side-project could make me ten times more proficient as a developer, I'd have laughed—and moved on.

Then I built Feedspress for Earnware, and everything changed. By the time I hit "Publish," I was standing at the foot of a massive coding mountain.

To be honest it feels like i didn't even put a dent in the climb, lol. but that small little climc that i did to taught me 3 Fundamentals:

##Putting Logic In The Server

I can already hear the Senior developers laughing at me about this.

But that is alright, because we all start somewhere.

If i was to give you the source code for my first simplistic project that i built called Mybooklyst

I putting everything in my client side code, to put it simply, all of my code was clientside

This not only caused my app to run slow, but it made my codebase look horrendous(it's gotten a bit better).

While building this new project i had made it my goal to start putting my bussiness logic on the server and just have my client side code on the client, which is what exactly i did.

server/
  controllers/
  lib/
  middleware/
  routes/
  scripts/
  types/
  index.ts
  routes.ts
  vite.ts

This is a common way that I learned to structure my code:

the services for api files

another thing that i learned was the importanece of stururing your api fetches in the correct way. originally i was making api calls to the backend

by making api calls that had had alot of code that was reuesed. so instead i put it into a singular file and then imported it into all of the api

files that i use. here is an example of what i do:

First, I created a reusable apiFetch function to handle the common logic for all API requests, such as setting headers and serializing the request body. This kept my code DRY (Don't Repeat Yourself) and made error handling much more consistent.

// lib/apiFetch.ts
export async function apiFetch(url: string, options: Omit<RequestInit, 'body'> & { body?: any } = {}) {
  const { body, ...rest } = options;
  const config: RequestInit = {
    headers: {
      'Content-Type': 'application/json',
    },
    ...rest,
  };
 
  if (body) {
    // Check if body is a plain object and not other BodyInit types
    if (typeof body === 'object' && body !== null && !(body instanceof Blob) && !(body instanceof FormData) && !(body instanceof URLSearchParams)) {
      config.body = JSON.stringify(body);
    } else {
      config.body = body;
    }
  }
 
  const response = await fetch(url, config);
 
  if (response.status === 204) {
    return { success: true };
  }
 
  const result = await response.json().catch(() => ({}));
 
  if (!response.ok) {
    throw new Error(result.message || `Request failed with status ${response.status}`);
  }
 
  return result;
}

With the apiFetch helper in place, my service file for managing RSS sources became much cleaner. Each function is now a simple, declarative wrapper around apiFetch, making the code easier to read and maintain.

// services/rssApi.ts
import { apiFetch } from '@/lib/apiFetch';
 
export async function fetchRssSources() {
  return apiFetch(`/api/management`, {
    method: "GET",
    credentials: "include",
  });
}
 
// Add a new RSS source for the current user
export async function addRssSource({ url, manual_file_id }: { url: string; manual_file_id: string }) {
  return apiFetch(`/api/management`, {
    method: "POST",
    credentials: "include",
    body: { url, manual_file_id },
  });
}
 
// Delete an RSS source by id for the current user
export async function deleteRssSource(id: string, body?: { manual_file_id: string }) {
  return apiFetch(`/api/management/${id}`, {
    method: "DELETE",
    headers: body ? { "Content-Type": "application/json" } : {},
    credentials: "include",
    ...(body && { body: body }),
  });
}

the logic behind good frontend standards.

one of my biggest accomplishments from this project was for the ability to export entire pages into different pages. I know that sounds stupid as

normally everyhting is exported to the app.tsx file, but coming from a guy who knew nothing about coding, it was a huge milestone for me that i started learning more fundementals.

the way i was looking at approaching my project was having a full on dashboard page that shows all the connecnt and articles in a singualar file. and you can click on

files (also known as feeds) to view individual new outputs. the only thing i had to do was to export the entire function into the dashboard page and put it in the html as jsx to

get the page to show.

Here is a small example of what I mean. In my main dashboard component, I could import the different page components I had built, just like this:

// app/dashboard/page.tsx
'use client'; // This component needs to be a client component to use state
 
import { useState } from 'react';
import ManualFeedIdPage from "@/app/dashboard/feeds/[manualFeedId]";
import FilteredFeedIdPage from "@/app/dashboard/feeds/[filteredfeedid]";
import ManualBestFeedIdPage from "@/app/dashboard/best-feeds/[manualBestFeedId]";
import FilteredBestFeedIdPage from "@/app/dashboard/best-feeds/[filteredBestFeedId]";
 
 
function DashboardView() {
  
  const [activeFeed, setActiveFeed] = useState({ type: 'manual', id: '123' });
 
  const renderActiveFeed = () => {
    // Since these are Next.js page components from dynamic routes, they expect a `params` prop.
    switch (activeFeed.type) {
      case 'manual':
        return <ManualFeedIdPage params={{ manualFeedId: activeFeed.id }} />;
      case 'filtered':
        return <FilteredFeedIdPage params={{ filteredfeedid: activeFeed.id }} />;
      case 'best-manual':
        return <ManualBestFeedIdPage params={{ manualBestFeedId: activeFeed.id }} />;
      case 'best-filtered':
        return <FilteredBestFeedIdPage params={{ filteredBestFeedId: activeFeed.id }} />;
      default:
        return <div>Please select a feed to view its content.</div>;
    }
  };
 
  return (
    <div>
      <h1>My Dashboard</h1>
     
      
      <main className="feed-content-wrapper">
        {renderActiveFeed()}
      </main>
    </div>
  );
}

By structuring my app this way, I could reuse entire pages as if they were simple building blocks. It was a simple idea, but it was a game-changer for how I thought about building UIs. This component-based approach is what makes modern frontend frameworks so powerful.