<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Kevin</title>
    <description>The latest articles on DEV Community by Kevin (@kevinnadar22).</description>
    <link>https://dev.to/kevinnadar22</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F842335%2F96181e95-aca9-4ca4-93de-f47871f10ed7.png</url>
      <title>DEV Community: Kevin</title>
      <link>https://dev.to/kevinnadar22</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kevinnadar22"/>
    <language>en</language>
    <item>
      <title>Using Async SQLAlchemy Inside Sync Celery Tasks</title>
      <dc:creator>Kevin</dc:creator>
      <pubDate>Sun, 22 Mar 2026 19:50:18 +0000</pubDate>
      <link>https://dev.to/kevinnadar22/using-async-sqlalchemy-inside-sync-celery-tasks-3eg4</link>
      <guid>https://dev.to/kevinnadar22/using-async-sqlalchemy-inside-sync-celery-tasks-3eg4</guid>
      <description>&lt;h1&gt;
  
  
  Introduction
&lt;/h1&gt;

&lt;p&gt;Let's be honest. You've built this beautiful, modern web application using &lt;strong&gt;FastAPI&lt;/strong&gt; and &lt;strong&gt;Async SQLAlchemy&lt;/strong&gt;. Everything is blazing fast and non-blocking. Then, you need to handle background jobs - sending emails, processing reports, or syncing with third-party APIs. So, you reach for &lt;strong&gt;Celery&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You try to run your trusty async database queries inside a Celery task, and suddenly, things get weird. You see errors about event loops, or the task just hangs. Why is this so hard?&lt;/p&gt;

&lt;p&gt;If you’ve been there, you’re in the right place. Let’s break down why Celery and async SQLAlchemy don’t play nicely out of the box and, more importantly, how to make them work together without losing your sanity.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Core Problem: Sync vs. Async Worlds
&lt;/h2&gt;

&lt;h3&gt;
  
  
  How Sync SQLAlchemy Works
&lt;/h3&gt;

&lt;p&gt;In a synchronous environment, database operations are blocking. When you execute a query using standard SQLAlchemy:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The driver sends a request to the database server via a TCP socket.&lt;/li&gt;
&lt;li&gt;The execution thread is blocked at the system level, waiting for data to be available on that socket.&lt;/li&gt;
&lt;li&gt;The CPU cannot perform any other work on that thread until the database response is fully received and parsed.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In Celery, this means a worker process or thread is completely occupied during the entire I/O wait. If you have many concurrent I/O-bound tasks, you quickly saturate your worker pool, leading to significant latency and resource waste.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is Celery?
&lt;/h3&gt;

&lt;p&gt;In one sentence: &lt;strong&gt;Celery is a distributed task queue that processes tasks synchronously (by default) using either multiprocessing, gevent, or threading.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;By default, if you give Celery an async function, it won’t know what to do with it. It expects a regular, synchronous function.&lt;/p&gt;




&lt;h2&gt;
  
  
  Async SQLAlchemy to the Rescue (But Wait...)
&lt;/h2&gt;

&lt;p&gt;Async SQLAlchemy solves this by leveraging non-blocking I/O. Instead of blocking the thread, it yields control back to the event loop while waiting for the database response.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;## This is the dream - non-blocking database call
&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nc"&gt;AsyncSession&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;scalar_one&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The issue? &lt;strong&gt;Celery is synchronous by nature.&lt;/strong&gt; You can’t just put an await inside a Celery task function because Celery doesn’t run an event loop.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Naïve Attempt (And Why It Fails)
&lt;/h3&gt;

&lt;p&gt;You might think, "I’ll just use asyncio.run() to run my async code inside the sync Celery task!"&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;celery&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;shared_task&lt;/span&gt;

&lt;span class="nd"&gt;@shared_task&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;process_user_task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# This seems clever, but it's a trap!
&lt;/span&gt;    &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;get_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works for one task, but it will eventually crash your database connection pool. Here’s why:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; asyncio.run() creates a brand new event loop to run your async function.&lt;/li&gt;
&lt;li&gt; Your async function opens a database connection from the pool.&lt;/li&gt;
&lt;li&gt; The function finishes, and asyncio.run() &lt;strong&gt;destroys the entire event loop&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt; The database connection was tied to that destroyed loop. When SQLAlchemy tries to return that connection to the pool, the connection is essentially "dead" or stuck in a closed loop.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;After a few tasks, your connection pool becomes corrupted, and you start seeing errors like&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;sqlalchemy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;TimeoutError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;QueuePool&lt;/span&gt; &lt;span class="n"&gt;limit&lt;/span&gt; &lt;span class="n"&gt;of&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="n"&gt;overflow&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="n"&gt;reached&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  The Solution: No Pool + async_to_sync
&lt;/h2&gt;

&lt;p&gt;To fix this, we need to change two things about how we handle database sessions for background tasks:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Don't use a connection pool.&lt;/strong&gt; Since each task will run in its own isolated event loop (created and destroyed), we should open a fresh connection for the task and close it when the task finishes. No pooling, no cross-loop contamination.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Use async_to_sync to bridge the gap.&lt;/strong&gt; This utility (from asgiref) allows us to run async code from a sync context without manually managing the event loop's lifecycle as poorly as asyncio.run() does.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Step 1: Configure a "No Pool" Session
&lt;/h3&gt;

&lt;p&gt;In your database configuration, create a specific session maker for your Celery tasks that uses &lt;code&gt;NullPool&lt;/code&gt;. This ensures that connections are closed when the session is closed, not kept alive.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;## db.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;sqlalchemy.ext.asyncio&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;create_async_engine&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;async_sessionmaker&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;sqlalchemy.pool&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;NullPool&lt;/span&gt;

&lt;span class="c1"&gt;## Your regular async engine (for FastAPI, etc.)
## This likely uses a pool (like AsyncAdaptedQueuePool)
&lt;/span&gt;&lt;span class="n"&gt;main_engine&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;create_async_engine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;postgresql+asyncpg://user:pass@localhost/db&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;pool_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;## Celery-specific engine with NO POOL
&lt;/span&gt;&lt;span class="n"&gt;celery_engine&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;create_async_engine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;postgresql+asyncpg://user:pass@localhost/db&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;poolclass&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;NullPool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# &amp;lt;-- This is the key!
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;## Session makers
&lt;/span&gt;&lt;span class="n"&gt;AsyncMainSession&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;async_sessionmaker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;main_engine&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;expire_on_commit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;AsyncCelerySession&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;async_sessionmaker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;celery_engine&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;expire_on_commit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: Use async_to_sync from asgiref
&lt;/h3&gt;

&lt;p&gt;Instead of creating a custom decorator, we can use the async_to_sync utility from the asgiref library. This is the industry-standard way to bridge sync and async code, handling the complexities of managing a per-thread event loop.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Call Your Async Logic Directly
&lt;/h3&gt;

&lt;p&gt;Write your core database logic as a standard &lt;code&gt;async&lt;/code&gt; function. Then, in your sync Celery task, call it directly using &lt;code&gt;async_to_sync&lt;/code&gt;. This keeps the Celery task minimal and makes the bridge explicit.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;celery&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;shared_task&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;asgiref.sync&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;async_to_sync&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;AsyncCelerySession&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;sqlalchemy&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;select&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Article&lt;/span&gt;


&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_run&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nc"&gt;AsyncCelerySession&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;## Perform non-blocking database queries
&lt;/span&gt;        &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Article&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pending&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="n"&gt;articles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;scalars&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;articles&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nd"&gt;@celery_app.task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;app.workers.run_article_pipeline&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;acks_late&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run_article_pipeline_task&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="c1"&gt;# Direct bridging without a decorator
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;async_to_sync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_run&lt;/span&gt;&lt;span class="p"&gt;)()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How This Works Internally
&lt;/h2&gt;

&lt;p&gt;Let's visualize the flow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Celery Worker&lt;/strong&gt; starts. It loads the &lt;code&gt;run_article_pipeline_task&lt;/code&gt; function as a standard synchronous function.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Task Executes:&lt;/strong&gt; The function body calls &lt;code&gt;async_to_sync(_run)()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Event Loop:&lt;/strong&gt; &lt;code&gt;async_to_sync&lt;/code&gt; manages the lifecycle of the event loop for the current thread and executes the &lt;code&gt;_run&lt;/code&gt; coroutine.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Database Connection:&lt;/strong&gt; Inside &lt;code&gt;_run&lt;/code&gt;, &lt;code&gt;AsyncCelerySession()&lt;/code&gt; creates a new connection using &lt;code&gt;NullPool&lt;/code&gt;. This avoids cross-loop connection contamination by opening a fresh TCP connection.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;The Wait:&lt;/strong&gt; The event loop handles the "await" points, allowing for efficient I/O management even within the sync Celery worker.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Cleanup:&lt;/strong&gt; Once the &lt;code&gt;async with&lt;/code&gt; block closes the session, &lt;code&gt;NullPool&lt;/code&gt; ensures the socket is physically closed. &lt;code&gt;async_to_sync&lt;/code&gt; then clears the loop state.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;No dead connections, no pool corruption, and you get to use your beautiful async SQLAlchemy code.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Note on Performance
&lt;/h2&gt;

&lt;p&gt;Using &lt;code&gt;NullPool&lt;/code&gt; and creating a new connection for &lt;em&gt;every&lt;/em&gt; task adds a small overhead (TCP handshake, authentication). For background tasks that run infrequently (like once every few seconds), this is perfectly fine.&lt;/p&gt;

&lt;p&gt;If you are running thousands of tasks per second, you might need a different architecture (like moving to a fully async task queue, or using &lt;code&gt;gevent&lt;/code&gt; workers with a sync SQLAlchemy pool). But for 90% of use cases, this "No Pool + &lt;code&gt;async_to_sync&lt;/code&gt;" pattern is the cleanest and most reliable way to reuse your async code in a sync Celery environment.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>celery</category>
      <category>sqlalchemy</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>A Guide to Designing REST APIs Like a Pro Engineer</title>
      <dc:creator>Kevin</dc:creator>
      <pubDate>Sun, 22 Mar 2026 14:16:37 +0000</pubDate>
      <link>https://dev.to/kevinnadar22/a-guide-to-designing-rest-apis-like-a-pro-engineer-791</link>
      <guid>https://dev.to/kevinnadar22/a-guide-to-designing-rest-apis-like-a-pro-engineer-791</guid>
      <description>&lt;h2&gt;
  
  
  How Good Engineers Design REST APIs
&lt;/h2&gt;

&lt;p&gt;When engineers start building APIs for the first time, the endpoints often look like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;POST /createBlog&lt;/li&gt;
&lt;li&gt;GET /getBlogs&lt;/li&gt;
&lt;li&gt;POST /deleteBlog&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This works at the beginning. The API does what it needs to do, and the frontend can communicate with the backend.&lt;/p&gt;

&lt;p&gt;But as the system grows, this style quickly becomes messy. New endpoints appear for every action, naming becomes inconsistent, and the API slowly turns into a collection of unrelated routes.&lt;/p&gt;

&lt;p&gt;This happens because the API is designed around &lt;strong&gt;actions instead of resources&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Good engineers approach API design differently. Instead of thinking about &lt;em&gt;what action the client wants to perform&lt;/em&gt;, they think about &lt;em&gt;what resource the system exposes&lt;/em&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Think in Terms of Resources
&lt;/h2&gt;

&lt;p&gt;Every application revolves around a few core entities.&lt;/p&gt;

&lt;p&gt;If you are building a blog platform, those entities might be users, blogs, and comments.&lt;/p&gt;

&lt;p&gt;Instead of creating endpoints for every action, the API is designed around these resources.&lt;/p&gt;

&lt;p&gt;For example, a beginner API might look like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;POST /createBlog&lt;/li&gt;
&lt;li&gt;GET /getBlogById&lt;/li&gt;
&lt;li&gt;POST /deleteBlog&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This style treats the API like a collection of functions.&lt;/p&gt;

&lt;p&gt;A resource-oriented design is much simpler:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;POST /blogs&lt;/li&gt;
&lt;li&gt;GET /blogs/1&lt;/li&gt;
&lt;li&gt;DELETE /blogs/1&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here the URL represents the &lt;strong&gt;resource&lt;/strong&gt;, while the HTTP method represents the &lt;strong&gt;action&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This makes the API predictable. Once someone understands one endpoint, they can guess the rest.&lt;/p&gt;




&lt;h2&gt;
  
  
  Let HTTP Do Its Job
&lt;/h2&gt;

&lt;p&gt;HTTP already provides verbs that describe what we want to do with resources.&lt;/p&gt;

&lt;p&gt;Instead of inventing custom actions in URLs, REST APIs rely on these standard methods.&lt;/p&gt;

&lt;p&gt;Creating a resource uses &lt;code&gt;POST&lt;/code&gt;, retrieving one uses &lt;code&gt;GET&lt;/code&gt;, updating uses &lt;code&gt;PUT&lt;/code&gt; or &lt;code&gt;PATCH&lt;/code&gt;, and deleting uses &lt;code&gt;DELETE&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For example, creating a blog might look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;POST /blogs
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The request body contains the blog data, and the server responds with 201 Created once the resource has been stored.&lt;/p&gt;

&lt;p&gt;Using these standard methods makes APIs predictable. Developers interacting with your system already know what to expect because the semantics come from HTTP itself.&lt;/p&gt;




&lt;h3&gt;
  
  
  PUT vs PATCH in REST APIs
&lt;/h3&gt;

&lt;p&gt;Both PUT and PATCH update resources, but they behave slightly differently.&lt;/p&gt;

&lt;p&gt;PUT usually replaces the entire resource. If you send a full blog object, the server overwrites the existing one.&lt;/p&gt;

&lt;p&gt;PATCH updates only the fields provided in the request.&lt;/p&gt;

&lt;p&gt;For example, if you only want to change the title of a blog post, PATCH is usually the better choice.&lt;/p&gt;

&lt;p&gt;Understanding this difference helps avoid unexpected behavior when APIs evolve.&lt;/p&gt;




&lt;h3&gt;
  
  
  Keep URLs Predictable
&lt;/h3&gt;

&lt;p&gt;A well-designed API should feel intuitive even before reading documentation.&lt;/p&gt;

&lt;p&gt;Most APIs treat endpoints as collections, which is why resource names are usually plural.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;/users&lt;/li&gt;
&lt;li&gt;/blogs&lt;/li&gt;
&lt;li&gt;/comments&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Even when requesting a single item, we are selecting something from that collection.&lt;/p&gt;

&lt;p&gt;/blogs/1&lt;/p&gt;

&lt;p&gt;This structure scales naturally as the system grows.&lt;/p&gt;

&lt;p&gt;If blogs later have comments, the relationship can be represented directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;GET /blogs/1/comments
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The URL now mirrors the data model of the system, making the API easier to understand.&lt;/p&gt;




&lt;h3&gt;
  
  
  REST API Naming Conventions
&lt;/h3&gt;

&lt;p&gt;Naming conventions are one of the simplest ways to make an API predictable.&lt;/p&gt;

&lt;p&gt;Most REST APIs follow a few common practices:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use plural resource names&lt;/li&gt;
&lt;li&gt;Use lowercase URLs&lt;/li&gt;
&lt;li&gt;Avoid verbs in endpoints&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;/blogs&lt;/li&gt;
&lt;li&gt;/blogs/42&lt;/li&gt;
&lt;li&gt;/users/5/comments&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The endpoint describes the resource hierarchy, while the HTTP method describes the action.&lt;/p&gt;

&lt;p&gt;This separation keeps APIs consistent as they grow.&lt;/p&gt;




&lt;h3&gt;
  
  
  Consistency Matters More Than Perfection
&lt;/h3&gt;

&lt;p&gt;One of the biggest differences between beginner APIs and well-designed ones is consistency.&lt;/p&gt;

&lt;p&gt;Clients interacting with an API should be able to predict how responses will look.&lt;/p&gt;

&lt;p&gt;Many teams adopt a consistent response structure so every endpoint behaves the same way.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The exact format is less important than the consistency.&lt;/p&gt;

&lt;p&gt;When responses follow a predictable structure, frontend developers and other services can integrate much faster.&lt;/p&gt;

&lt;p&gt;The same principle applies to naming conventions. Whether you choose camelCase or snake_case, the important part is using the same style everywhere.&lt;/p&gt;




&lt;h3&gt;
  
  
  Idempotency in REST APIs
&lt;/h3&gt;

&lt;p&gt;A concept that becomes important in real systems is idempotency.&lt;/p&gt;

&lt;p&gt;An operation is idempotent if repeating it multiple times produces the same result.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;DELETE /blogs/1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the blog is deleted once, calling the same request again should not change the system further. The blog is already gone.&lt;/p&gt;

&lt;p&gt;This property becomes important when network failures occur. Clients often retry requests, and idempotent operations ensure that retries do not create unexpected side effects.&lt;/p&gt;

&lt;p&gt;GET, PUT, and DELETE are typically idempotent, while POST usually is not.&lt;/p&gt;




&lt;h3&gt;
  
  
  Designing APIs for Real Systems
&lt;/h3&gt;

&lt;p&gt;In real applications, endpoints rarely return just one object. They often return collections, and those collections can grow large.&lt;/p&gt;

&lt;p&gt;Instead of returning everything at once, APIs usually support pagination.&lt;/p&gt;

&lt;p&gt;A typical request might look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;GET /blogs?page=1&amp;amp;limit=10
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This allows the client to request manageable chunks of data, improving performance and reducing load on the server.&lt;/p&gt;

&lt;p&gt;APIs also commonly support filtering and sorting.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;GET /blogs?authorId=42&amp;amp;status=published&amp;amp;sort=-createdAt
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This request asks the server to return blogs written by author 42, filter only published posts, and sort them by newest first.&lt;/p&gt;

&lt;p&gt;Designing flexible query parameters like this allows APIs to evolve without creating dozens of new endpoints.&lt;/p&gt;




&lt;h3&gt;
  
  
  APIs Change Over Time
&lt;/h3&gt;

&lt;p&gt;No API stays the same forever. New features appear, data models evolve, and sometimes breaking changes are unavoidable.&lt;/p&gt;

&lt;p&gt;To avoid disrupting existing clients, APIs are often versioned.&lt;/p&gt;

&lt;p&gt;A common approach is including the version in the URL:&lt;/p&gt;

&lt;p&gt;/api/v1/blogs&lt;/p&gt;

&lt;p&gt;If a future change requires a different structure, a new version can be introduced without affecting older clients.&lt;/p&gt;

&lt;p&gt;Versioning allows systems to evolve while maintaining stability for applications already relying on the API.&lt;/p&gt;




&lt;h3&gt;
  
  
  REST API Design Checklist
&lt;/h3&gt;

&lt;p&gt;When designing APIs, a few principles consistently lead to better systems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Design around resources&lt;/li&gt;
&lt;li&gt;Use HTTP methods correctly&lt;/li&gt;
&lt;li&gt;Keep URLs predictable&lt;/li&gt;
&lt;li&gt;Maintain consistent response formats&lt;/li&gt;
&lt;li&gt;Support pagination and filtering&lt;/li&gt;
&lt;li&gt;Version APIs when breaking changes occur&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Following these principles keeps APIs understandable as systems grow.&lt;/p&gt;




&lt;h1&gt;
  
  
  Final Thoughts
&lt;/h1&gt;

&lt;p&gt;Designing a good REST API is not about memorizing a long list of rules.&lt;/p&gt;

&lt;p&gt;It is about building interfaces that are predictable, consistent, and easy for other engineers to understand.&lt;/p&gt;

&lt;p&gt;When APIs are designed around resources, use HTTP correctly, and maintain clear URL structures, they become much easier to scale and maintain over time.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>backenddevelopment</category>
      <category>api</category>
      <category>restapi</category>
    </item>
    <item>
      <title>Responsive SignUp Page</title>
      <dc:creator>Kevin</dc:creator>
      <pubDate>Tue, 05 Apr 2022 09:10:23 +0000</pubDate>
      <link>https://dev.to/kevinnadar22/responsive-signup-page-5hig</link>
      <guid>https://dev.to/kevinnadar22/responsive-signup-page-5hig</guid>
      <description>&lt;p&gt;Responsive SignUp Page Design with Social Icons&lt;/p&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/kevinnadar22/embed/poppXWX?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

</description>
      <category>codepen</category>
    </item>
  </channel>
</rss>
