<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Cosmin Marian Paduraru]]></title><description><![CDATA[Cosmin Marian Paduraru]]></description><link>https://blog.nimsoc.com</link><generator>RSS for Node</generator><lastBuildDate>Tue, 27 Oct 2026 15:00:03 GMT</lastBuildDate><atom:link href="https://blog.nimsoc.com/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[I Deleted My Backend and Saved $48/year]]></title><description><![CDATA[Most side projects don't need a backend.Mine definitely didn't. I have a webpage named TFT Double Up that finds the best compositions and pairs so players…]]></description><link>https://blog.nimsoc.com/i-deleted-my-backend-and-saved-48-year</link><guid isPermaLink="true">https://blog.nimsoc.com/i-deleted-my-backend-and-saved-48-year</guid><dc:creator><![CDATA[Cosmin Marian Paduraru]]></dc:creator><pubDate>Tue, 17 Mar 2026 22:55:56 GMT</pubDate><content:encoded><![CDATA[<p>Most side projects don't need a backend.<br />Mine definitely didn't.</p>
<h2>Context</h2>
<p>I have a webpage named <a href="https://tftdoubleup.com/">TFT Double Up</a> that finds the best compositions and pairs so players can coordinate their strategies without manually checking conflicts.</p>
<p>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.</p>
<p>At first I created custom backend API hosted in <a href="https://www.digitalocean.com/pricing/droplets#basic-droplets">Digital Ocean</a> and when user asked opened the webpage I was returning the results and caching them for 1 day. <em>You can see that code in</em> <a href="https://github.com/cosminpm/tft-double-up/blob/360c3dac42dd8a6993318fdf8d9b50f7bc646955/app/services/tft_api_fetcher/router.py#L24"><em>these two lines of code</em></a>:</p>
<pre><code class="language-python">@cache(expire=86400) if not settings.debug else (lambda f: f)
async def get_best_pairs(request: Request) -&gt; JSONResponse:
</code></pre>
<p>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.</p>
<p>As each request was almost the same, only the data was changing from one day to the other, I thought about using <a href="https://docs.github.com/en/billing/concepts/product-billing/github-actions">GitHub actions</a>.</p>
<p>I found in their documentation that: <em>GitHub Actions usage is</em> <em><strong>free</strong></em> <em>for</em> <em><strong>self-hosted runners</strong></em> <em>and for</em> <em><strong>public repositories</strong></em> <em>that use standard GitHub-hosted runners. See</em> <a href="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"><em>Choosing the runner for a job</em></a><em>.</em></p>
<p>As my API was basically a <code>chron job</code> I refactored the code to include a daily workflow and save it in a <code>json</code> file.</p>
<h2>Key insight</h2>
<p>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 <em>cron job</em>, not a real backend service.</p>
<h2>Refactor</h2>
<p>The refactor was quite simple, first of all I created a <a href="https://github.com/cosminpm/tft-double-up/blob/main/scripts/daily_fetch.py">new file named</a> <code>daily_fetch.py</code> to call the two functions and compare if the results are different from the previous day, if they are different change the <code>current.json</code> and create a new entry in the <code>historic</code> folder.</p>
<p>Secondly in the <code>.github/workflows</code> directory and to call the <code>daily_fetch.py</code> and push it to the data folder if there are differences. The only remarkable point you have to change is to allow <em>Read and Write</em> permissions in the <code>settings/actions</code>.</p>
<p>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.</p>
<h2>Architecture diagram</h2>
<p><strong>Before</strong>: <code>User → Frontend → Backend (Digital Ocean) → Cache → API</code></p>
<p><strong>After</strong>: <code>GitHub Action (daily) → Generate JSON → commit to repo → Frontend fetches static data</code></p>
<h2>When this works</h2>
<p>This pattern works well when:</p>
<ul>
<li><p>Data changes infrequently</p>
</li>
<li><p>Results are identical for all/most users</p>
</li>
<li><p>Real time updates are not needed</p>
</li>
</ul>
<h2>Result</h2>
<p>Before</p>
<ul>
<li><p>DigitalOcean - $4/month</p>
</li>
<li><p>$48/year After</p>
</li>
<li><p>GitHub actions - $0</p>
</li>
</ul>
<p><strong>If your backend only runs once per day, it's not really a backend.</strong></p>
]]></content:encoded></item><item><title><![CDATA[Why Your Alias Doesn't Work in WHERE]]></title><description><![CDATA[Imagine you are a company that sells headphones in Spain and we want to expand to France. You have two tables: orders: a user bought a headphone and we…]]></description><link>https://blog.nimsoc.com/why-your-alias-doesnt-work-in-where</link><guid isPermaLink="true">https://blog.nimsoc.com/why-your-alias-doesnt-work-in-where</guid><category><![CDATA[SQL]]></category><category><![CDATA[Bugs and Errors]]></category><dc:creator><![CDATA[Cosmin Marian Paduraru]]></dc:creator><pubDate>Thu, 15 Jan 2026 17:19:26 GMT</pubDate><content:encoded><![CDATA[<p>Imagine you are a company that sells headphones in Spain and we want to expand to France.</p>
<p>You have two tables:</p>
<ul>
<li><p><code>orders</code>: a user bought a headphone and we save it in an order.</p>
</li>
<li><p><code>addresses</code>: responsible for saving the different addresses of the user.</p>
</li>
</ul>
<p>And we will fetch <code>country_code</code> for our endpoint.</p>
<p>The column <code>country_code</code> is not ready yet and it's filled with <code>NULL</code>s. So we add <code>'temporary_code' AS country_code</code> to have the filter logic ready and we do: <code>WHERE country_code LIKE '%temporary_code%'</code> .</p>
<pre><code class="lang-sql"><span class="hljs-keyword">SELECT</span>  order_id,  
        address_id,  
        <span class="hljs-keyword">size</span>,  
        street,  
        country,  
        <span class="hljs-string">'temporary_code'</span> <span class="hljs-keyword">AS</span> country_code  
<span class="hljs-keyword">FROM</span> orders  
    <span class="hljs-keyword">LEFT</span> <span class="hljs-keyword">JOIN</span> addresses <span class="hljs-keyword">ON</span> orders.address_id = addresses.id
<span class="hljs-keyword">WHERE</span> country_code <span class="hljs-keyword">LIKE</span> <span class="hljs-string">'%temporary_code%'</span>
</code></pre>
<p>But something is not working correctly... Why?</p>
<p>The order in a SQL execution is:</p>
<ol>
<li><p>FROM</p>
</li>
<li><p>WHERE</p>
</li>
<li><p>SELECT</p>
</li>
</ol>
<p>So in this case we will be filtering by the <code>country_code</code> column that already exists inside the <code>addresses</code> table and not the one in <code>SELECT</code>, and this will return empty.</p>
<p>Temporarily you could write:</p>
<pre><code class="lang-SQL"><span class="hljs-keyword">SELECT</span>  order_id,  
        address_id,  
        <span class="hljs-keyword">size</span>,  
        street,  
        country,  
        <span class="hljs-string">'temporary_code'</span> <span class="hljs-keyword">AS</span> country_code  
<span class="hljs-keyword">FROM</span> orders  
    <span class="hljs-keyword">LEFT</span> <span class="hljs-keyword">JOIN</span> addresses <span class="hljs-keyword">ON</span> orders.address_id = addresses.id
<span class="hljs-keyword">WHERE</span> <span class="hljs-string">'temporary_code'</span> <span class="hljs-keyword">LIKE</span> <span class="hljs-string">'%temporary_code%'</span>
</code></pre>
]]></content:encoded></item><item><title><![CDATA[How to Recover the Model from a Subquery with SQLAlchemy]]></title><description><![CDATA[Disclaimer: This guide offers an alternative explanation to the official SQLAlchemy documentation on subqueries, providing more visual examples and simpler…]]></description><link>https://blog.nimsoc.com/how-to-recover-the-model-from-a-subquery-with-sqlalchemy</link><guid isPermaLink="true">https://blog.nimsoc.com/how-to-recover-the-model-from-a-subquery-with-sqlalchemy</guid><category><![CDATA[Python]]></category><category><![CDATA[sqlalchemy]]></category><category><![CDATA[SQL]]></category><dc:creator><![CDATA[Cosmin Marian Paduraru]]></dc:creator><pubDate>Thu, 15 Aug 2024 12:26:17 GMT</pubDate><content:encoded><![CDATA[<p><strong>Disclaimer:</strong> This guide offers an alternative explanation to the official SQLAlchemy <a target="_blank" href="https://docs.sqlalchemy.org/en/13/orm/tutorial.html#using-subqueries">documentation on subqueries</a>, providing more visual examples and simpler explanations. The information is based on <code>SQLAlchemy==2.0.30</code>, and newer versions or features may affect its relevance over time.</p>
<h2 id="heading-solution-overview">Solution Overview</h2>
<p>If you're only interested in the solution, here it is:</p>
<pre><code class="lang-python"><span class="hljs-comment"># Build the main query</span>
query: Query = session.query(Post)

<span class="hljs-comment"># Build the subquery</span>
subquery = (
    self.session.query(Reaction)
    .filter(Reaction.user_id == user_id)
    .subquery()
)

<span class="hljs-comment"># Join with the subquery</span>
query = query.outerjoin(subquery, Post.id == subquery.c.post_id)
alias_reaction = aliased(Reaction, subquery)
query = query.add_entity(alias_reaction)
</code></pre>
<h2 id="heading-background">Background</h2>
<p>In many backend systems, we have different models for validation <a target="_blank" href="https://docs.pydantic.dev/latest/">(Pydantic)</a> and for models in database representation <a target="_blank" href="https://www.sqlalchemy.org">(SQLAlchemy)</a>. Although some libraries like <a target="_blank" href="https://sqlmodel.tiangolo.com">SQLModel</a> attempt to bridge this gap, they are still in early stages.</p>
<p>Consider a scenario where you have two SQLAlchemy models: <em>Post (P)</em> and <em>Reaction (R)</em>. Here, P represents a social network post, and R represents a user's reaction to a post.</p>
<p>Suppose you need to query all P along with the R of a specific <em>User (U)</em>. Directly joining all posts with all reactions and filtering by <code>user_id</code> may be inefficient due to the large volume of data. Instead, you can optimize this by filtering reactions first and then performing the join.</p>
<p>The SQL for this optimized approach looks like:</p>
<pre><code class="lang-sql"><span class="hljs-keyword">SELECT</span> *
<span class="hljs-keyword">FROM</span> 
  posts p
<span class="hljs-keyword">LEFT</span> <span class="hljs-keyword">JOIN</span> 
  (<span class="hljs-keyword">SELECT</span> * 
   <span class="hljs-keyword">FROM</span> reactions 
   <span class="hljs-keyword">WHERE</span> user_id = {{user_id}}
  ) r
<span class="hljs-keyword">ON</span> 
  r.post_id = p.id
</code></pre>
<p>The query itself it's not hard, but what I have found difficult was to found a way of keeping both models when using SQLAlchemy.</p>
<p>First we do a subquery, this would be equivalent to the subquery we mentioned before to speed up the join operation.</p>
<pre><code class="lang-python"><span class="hljs-comment"># Build the main query</span>
query: Query = session.query(Post)

<span class="hljs-comment"># Build the subquery</span>
subquery = (
    self.session.query(Reaction)
    .filter(Reaction.user_id == user_id)
    .subquery()
)
</code></pre>
<p>This part it's not something hard and we can easily find <a target="_blank" href="https://docs.sqlalchemy.org/en/20/orm/queryguide/query.html#sqlalchemy.orm.Query.subquery">documentation</a> about it. The tricky part comes now, I had a hard momment finding a way to return both models in one single return. I could have returned the model P and the all the fields from R, but remember, we want a return with only both models, so it's clean and concise.</p>
<p>After some investigation I discovered that you need to alias the subquery, what I mean by this is that you need to specify the model that will be returned and then add the entity to the original query. Below you can see how it's done.</p>
<pre><code class="lang-python"><span class="hljs-comment"># Join with the subquery</span>
query = query.outerjoin(subquery, Post.id == subquery.c.post_id)
alias_reaction = aliased(Reaction, subquery)
query = query.add_entity(alias_reaction)
<span class="hljs-keyword">return</span> query.all()
</code></pre>
<p>Then we just do:</p>
<pre><code class="lang-python">post_models: list[PostModel] = [PostModel.model_validate(post) <span class="hljs-keyword">for</span> post, _ <span class="hljs-keyword">in</span> posts]
reactions_models: list[ReactionModel] = [ReactionModel.model_validate(reaction) <span class="hljs-keyword">for</span> _, reaction <span class="hljs-keyword">in</span> reactions]
</code></pre>
<p>It's interesting to understand why this happens, when you do <code>SELECT *</code> behind it deconstructs the model and returns you all the fields. But now in SQLAlchemy you don't want to lose these models as you may want to return them, and that's basically the reason.</p>
<p>It was an interesting investigation that was not easy to find and I wanted to share with all of you.</p>
]]></content:encoded></item></channel></rss>