Go Backend for dApps: Reliable Indexer, API & WebSocket for DeFi/NFT
Picture this: your DeFi protocol processes 10,000 transactions per minute, but the Node.js backend can't handle the WebSocket load—connection pools drop, events are lost, users complain about delays. We've seen this dozens of times and switched to Go. Result: stable operation at 50,000+ events per minute with 3x less memory consumption. Our team of 12 engineers with over 50 years of combined blockchain experience has completed 80+ projects for DeFi, NFT, and GameFi.
Go isn't the obvious choice in the blockchain context—most tutorials use Node.js/TypeScript. But in practice, Go wins where reliability under load matters: event indexing, node webhook processing, off-chain keeper and bot components. Geth is written in Go, and go-ethereum is the most mature low-level library for EVM work. According to benchmarks from the official go-ethereum repository, log processing throughput on Go is 3-4 times higher than Node.js under the same resource costs.
Why Go for dApp Backend?
Go delivers 2-5x more throughput on the same resources compared to Node.js (from our load tests). You won't have to worry about callback hell or event loop—Go's concurrency model with goroutines and channels maps perfectly to streaming blockchain event processing.
| Metric | Go | Node.js |
|---|---|---|
| Throughput (events/sec) | 12,000 | 3,500 |
| Memory per 10K WebSocket | 120 MB | 450 MB |
| API response time (p95) | 15 ms | 45 ms |
Key go-ethereum Patterns
Connection and Reading
client, err := ethclient.Dial("wss://eth-mainnet.g.alchemy.com/v2/KEY") // For production—fallback between multiple providers token, _ := token.NewToken(tokenAddress, client) balance, _ := token.BalanceOf(nil, userAddress) // typed For reading contract data we use abigen—a typed Go binding generator from ABI. This eliminates interface{} and catches errors at compile time.
Event Subscriptions
WebSocket event subscription is the foundation of indexers:
query := ethereum.FilterQuery{ Addresses: []common.Address{contractAddress}, Topics: [][]common.Hash{{ crypto.Keccak256Hash([]byte("Transfer(address,address,uint256)")), }}, } logs := make(chan types.Log) sub, err := client.SubscribeFilterLogs(ctx, query, logs) for { select { case err := <-sub.Err(): // reconnect logic case log := <-logs: processTransferEvent(log) } } Critical: WebSocket connections drop. You need reconnect logic with exponential backoff. For production—a separate goroutine monitors subscription state and recreates it on disconnection.
Indexer Service Architecture
A typical use case: collect smart contract events, store them in PostgreSQL, provide REST/GraphQL API for the frontend.
Service structure:
cmd/ indexer/main.go — entry point api/main.go — HTTP server internal/ indexer/ — event processing logic repository/ — data layer (PostgreSQL) blockchain/ — go-ethereum client api/handlers/ — HTTP handlers How to Handle Block Reorganization?
This is the most non-obvious issue for developers without blockchain experience. Blocks can be reorganized—a transaction in block 100 might disappear if a reorg occurs. A naive indexer that ignores reorgs will accumulate incorrect data.
Solution: don't mark blocks as "finalized" immediately. Wait for N confirmations (12 for Ethereum, 3 for Polygon, 1 for Arbitrum with its finalization). Store block_hash along with event data. On detecting a reorg, roll back all records with changed block_hash.
type IndexedEvent struct { ID int64 BlockNumber uint64 BlockHash common.Hash TxHash common.Hash LogIndex uint Data []byte Finalized bool } Periodically query eth_getBlockByNumber for the latest N blocks and compare block_hash with stored ones.
Transaction Signing and Sending
For off-chain components (keepers, automated transactions)—manage private keys in the backend:
privateKey, _ := crypto.HexToECDSA(os.Getenv("PRIVATE_KEY")) auth, _ := bind.NewKeyedTransactorWithChainID(privateKey, chainID) // EIP-1559 pricing tip, _ := client.SuggestGasTipCap(ctx) auth.GasTipCap = tip auth.GasFeeCap = new(big.Int).Add(baseFee, tip) // baseFee from latest block tx, err := contract.SomeMethod(auth, arg1, arg2) For production, use AWS KMS or HashiCorp Vault instead of an environment variable. Nonce management is a separate topic: for parallel transaction sending you need a nonce manager that atomically issues the next nonce and handles dropped/stuck transactions.
API Layer
r := chi.NewRouter() r.Use(middleware.Logger) r.Use(middleware.RealIP) r.Use(cors.Handler(cors.Options{ AllowedOrigins: []string{"https://app.example.com"}, AllowedMethods: []string{"GET", "POST"}, })) r.Get("/api/v1/events", handlers.GetEvents) r.Get("/api/v1/user/{address}/positions", handlers.GetUserPositions) WebSocket endpoint for real-time updates—gorilla/websocket or nhooyr.io/websocket. One goroutine per connection, channel-based broadcast from indexer to WebSocket clients.
What's Included
- Development of indexer service with go-ethereum
- REST/GraphQL API with documentation (OpenAPI)
- WebSocket for real-time data
- Nonce management and reorg handling
- Historical event backfill
- Integration with PostgreSQL/Redis
- Docker/Kubernetes deployment + CI/CD (GitOps with ArgoCD)
- Monitoring with Prometheus/Grafana, logs with Loki
- Code review and 3 months of support
Timeline Estimates
| Phase | Duration | Includes |
|---|---|---|
| Basic version | 3-4 days | Indexer + 5 endpoints, 1 contract |
| Full service | 1.5-2 weeks | Reorg, WebSocket, nonce manager, backfill |
| Complex project | from 3 weeks | Multi-contract, keeper, oracle integration |
Contact us for a free assessment of your project—we'll calculate exact timelines and provide architecture recommendations.
Order your dApp backend development in Go: we'll prepare the architecture, identify bottlenecks, and offer a turnkey solution with a 99.9% SLA guarantee.







