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.







