Python Backend for dApps: Async Architecture Overview

Building dApp Backends with Python: Async Architecture Imagine: a DeFi aggregator needs to update prices from 20 pools every 5 minutes, calculate impermanent loss, and send transactions with minimal slippage. Synchronous Flask won't cut it — the blockchain produces a block every 12–15 seconds, an

Blockchain Development Services

Frequently Asked Questions

Latest works

  • image_website-b2b-advance_0.webp
    B2B ADVANCE company website development
    1441
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1301
  • image_websites_belfingroup_462_0.webp
    Website development for BELFINGROUP
    998
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1267
  • image_logo-advance_0.webp
    B2B Advance company logo design
    713
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    1003

Building dApp Backends with Python: Async Architecture

Imagine: a DeFi aggregator needs to update prices from 20 pools every 5 minutes, calculate impermanent loss, and send transactions with minimal slippage. Synchronous Flask won't cut it — the blockchain produces a block every 12–15 seconds, and each HTTP request waits for an RPC response. Python with an async stack solves this: web3.py for the blockchain, FastAPI for the API, Celery for background tasks. Over a decade of experience, we've delivered 10+ such projects: from NFT marketplaces to DeFi aggregators with APY calculators.

web3.py documentation provides a smooth interface to interact with Ethereum nodes.

How web3.py Simplifies Blockchain Interaction

web3.py is a mature library for working with EVM networks. Key pain points: checksum address validation and PoA middleware. Without Web3.to_checksum_address(), any call to an external source will throw an error. For Polygon and BNB Chain, we always attach geth_poa_middleware. Example setup:

from web3 import Web3 from web3.middleware import geth_poa_middleware w3 = Web3(Web3.HTTPProvider("https://eth-mainnet.g.alchemy.com/v2/KEY")) w3.middleware_onion.inject(geth_poa_middleware, layer=0) balance = w3.eth.get_balance("0xChecksumAddress") contract = w3.eth.contract(address=checksum_address, abi=ABI) result = contract.functions.balanceOf(address).call() 

How Asynchronicity and Celery Solve Performance Issues

Synchronous blockchain calls block the event loop. We use FastAPI + async web3, and offload background tasks to Celery. Async web3 is 10x faster than synchronous requests, boosting throughput from ~50 to 500+ req/s. Celery tasks handle long-running operations: sending transactions, indexing events, syncing prices. Example task with retry:

from celery import Celery from celery.schedules import crontab celery_app = Celery("dapp", broker="redis://localhost:6379/0") @celery_app.task(bind=True, max_retries=3) def send_transaction(self, contract_address, function_name, args): try: contract = w3.eth.contract(address=contract_address, abi=ABI) tx_hash = contract.functions[function_name](*args).transact({ "from": hot_wallet.address, "gas": 200000 }) return {"tx_hash": tx_hash.hex(), "status": "pending"} except Exception as exc: raise self.retry(exc=exc, countdown=30) celery_app.conf.beat_schedule = { "sync-prices": { "task": "tasks.sync_token_prices", "schedule": crontab(minute="*/5") } } 

Key dApp Backend Challenges

Nonce management. When sending transactions in parallel, two workers may read the same nonce — one transaction gets stuck. Solution: Redis locks with TTL or a nonce pool. Gas optimization. Every extra eth_call or eth_sendTransaction costs money. Caching data at the API layer cuts costs by 30–40%, saving approximately $500/month on infrastructure for medium-scale projects. Event indexing. WebSocket subscriptions are unreliable in production — we use Alchemy Notify + Celery tasks for resync on failures.

More on nonce managementNonce is a transaction counter from one address. Without locking, two workers may send the same nonce. We use Redis locks: before sending, a worker grabs a lock on the address, sends the transaction, and releases it. TTL ensures the lock doesn't persist if the worker crashes.

Our Stack and Code Examples

Feature Synchronous (Flask + requests) Async (FastAPI + async web3)
Throughput ~50 req/s 500+ req/s
Event loop blocking Yes No
Celery Required Required, but less critical
Debug complexity Low Medium
Use cases Simple proxies, low load High-load DeFi, real-time

FastAPI with Pydantic v2 for validation:

from fastapi import FastAPI, HTTPException from pydantic import BaseModel, validator import re class TransactionRequest(BaseModel): address: str amount: str @validator("address") def validate_eth_address(cls, v): if not re.match(r"^0x[a-fA-F0-9]{40}$", v): raise ValueError("Invalid Ethereum address") return Web3.to_checksum_address(v) app = FastAPI() @app.get("/api/balance/{address}") async def get_balance(address: str): try: checksum = Web3.to_checksum_address(address) except ValueError: raise HTTPException(status_code=400, detail="Invalid address") balance_wei = w3.eth.get_balance(checksum) return { "address": checksum, "balance_eth": Web3.from_wei(balance_wei, "ether"), "balance_wei": str(balance_wei) } 

SQLAlchemy + PostgreSQL storing wei as string:

from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession from sqlalchemy.orm import DeclarativeBase, mapped_column, Mapped from datetime import datetime class Base(DeclarativeBase): pass class Transaction(Base): __tablename__ = "transactions" id: Mapped[int] = mapped_column(primary_key=True) tx_hash: Mapped[str] = mapped_column(unique=True, index=True) from_address: Mapped[str] = mapped_column(index=True) to_address: Mapped[str] = mapped_column(index=True) value_wei: Mapped[str] # string to avoid precision loss block_number: Mapped[int] = mapped_column(index=True) timestamp: Mapped[datetime] status: Mapped[str] 

Our Process

  1. Analysis. We study smart contracts, API requirements, business logic. Create a technical specification.
  2. Design. Define architecture: DB structure, API methods, Celery tasks, signing scheme.
  3. Implementation. Write code, cover with tests (pytest), integrate with the blockchain.
  4. Testing. Deploy on testnet, verify scenarios: transaction sending, event handling, recovery after failures.
  5. Deployment. Deploy to production with Docker, set up monitoring (Grafana, Loki).

Timeline Estimates

Stage Duration
Base architecture, web3.py clients, REST API (read-only), PostgreSQL 1 week
Celery tasks, indexer, SIWE authentication, transaction signing 1 week
Complex business logic (analytics, ML) from 3 days

Full backend — from 1.5 to 2 weeks. Pricing is determined individually (typically starting at $8,000). This architecture reduces infrastructure costs by 30–40%, saving over $2,000 per year for mid-sized projects.

Common Mistakes in Python dApp Backend Development

  • Forgetting nonce management. Without synchronization, parallel requests cause stuck transactions.
  • Using Decimal for wei. Store as string to avoid precision loss.
  • Not adding middleware for PoA networks. Otherwise get_block throws an error.
  • Synchronous requests in API. They block the event loop, cutting throughput by 10x.

What You Get

  • Architecture tailored to your business logic.
  • REST API with OpenAPI documentation.
  • Event indexer writing to PostgreSQL.
  • Background tasks: transaction sending, price sync, health checks.
  • Secure signing with hot wallet or integration with Vault/AWS KMS.
  • Docker containerization and deployment documentation.
  • 3-month code warranty and post-delivery support.

We build dApp backends in Python using web3.py, FastAPI, and Celery. Contact us for a consultation — we'll help choose the architecture and estimate timelines. Request your dApp backend development today.