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 management
Nonce 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
- Analysis. We study smart contracts, API requirements, business logic. Create a technical specification.
- Design. Define architecture: DB structure, API methods, Celery tasks, signing scheme.
- Implementation. Write code, cover with tests (pytest), integrate with the blockchain.
- Testing. Deploy on testnet, verify scenarios: transaction sending, event handling, recovery after failures.
- 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_blockthrows 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.







