# I Deleted My Backend and Saved $48/year

Most side projects don't need a backend.  
Mine definitely didn't.

## Context

I have a webpage named [TFT Double Up](https://tftdoubleup.com/) that finds the best compositions and pairs so players can coordinate their strategies without manually checking conflicts.

I did this project as a side project because I was playing with one friend and we did this manually by looking at the best pairs and trying to not have conflicts.

At first I created custom backend API hosted in [Digital Ocean](https://www.digitalocean.com/pricing/droplets#basic-droplets) and when user asked opened the webpage I was returning the results and caching them for 1 day. *You can see that code in* [*these two lines of code*](https://github.com/cosminpm/tft-double-up/blob/360c3dac42dd8a6993318fdf8d9b50f7bc646955/app/services/tft_api_fetcher/router.py#L24):

```python
@cache(expire=86400) if not settings.debug else (lambda f: f)
async def get_best_pairs(request: Request) -> JSONResponse:
```

I didn't play this very often, but it was still a useful webpage. Four dollars is not a lot, over 12 months it's 48 dollars, which was enough to make me think about finding a free alternative.

As each request was almost the same, only the data was changing from one day to the other, I thought about using [GitHub actions](https://docs.github.com/en/billing/concepts/product-billing/github-actions).

I found in their documentation that: *GitHub Actions usage is* ***free*** *for* ***self-hosted runners*** *and for* ***public repositories*** *that use standard GitHub-hosted runners. See* [*Choosing the runner for a job*](https://docs.github.com/en/actions/writing-workflows/choosing-where-your-workflow-runs/choosing-the-runner-for-a-job#standard-github-hosted-runners-for-public-repositories)*.*

As my API was basically a `chron job` I refactored the code to include a daily workflow and save it in a `json` file.

## Key insight

All users were requesting the same data. The only thing changing was the dataset, which updated once per day. That meant my API was effectively acting as a *cron job*, not a real backend service.

## Refactor

The refactor was quite simple, first of all I created a [new file named](https://github.com/cosminpm/tft-double-up/blob/main/scripts/daily_fetch.py) `daily_fetch.py` to call the two functions and compare if the results are different from the previous day, if they are different change the `current.json` and create a new entry in the `historic` folder.

Secondly in the `.github/workflows` directory and to call the `daily_fetch.py` and push it to the data folder if there are differences. The only remarkable point you have to change is to allow *Read and Write* permissions in the `settings/actions`.

I also changed the FE to fetch the data from directly from the generated JSON then I turned off my Digital Ocean server and now I'm saving 4 dollars per month, which is not a lot but it was worth it as the change was not as big as I originally thought.

## Architecture diagram

**Before**: `User → Frontend → Backend (Digital Ocean) → Cache → API`

**After**: `GitHub Action (daily) → Generate JSON → commit to repo → Frontend fetches static data`

## When this works

This pattern works well when:

*   Data changes infrequently
    
*   Results are identical for all/most users
    
*   Real time updates are not needed
    

## Result

Before

*   DigitalOcean - $4/month
    
*   $48/year After
    
*   GitHub actions - $0
    

**If your backend only runs once per day, it's not really a backend.**
