We build the server-side of dApps on Node.js for projects where on-chain logic alone isn't enough. If you need a private RPC proxy, SIWE authentication, an event indexer, or a gasless relayer—we’ll create the entire infrastructure from scratch, turnkey. A backend becomes indispensable when you need: aggregation of data from multiple sources, caching expensive on-chain queries (saving up to 40% on RPC costs), gasless transactions (relayer), and signature verification. Our stack is modern: Fastify, ethers.js/viem, Redis, Prisma. We have over 10 years of blockchain development experience and 50+ completed dApp projects. Trusted by startups and enterprises, our team delivers reliable solutions with a 100% success rate. Typical backend development cost starts at $5,000 for a base project. If you need a reliable dApp backend, request a consultation—we’ll calculate the scope and timeline.
Key Components of a Node.js dApp Backend
RPC Abstraction Layer
Calling Infura/Alchemy directly from the frontend exposes your API key. A backend proxies RPC calls, adding caching and rate limiting:
import { JsonRpcProvider, Contract } from 'ethers'; import Fastify from 'fastify'; const provider = new JsonRpcProvider(process.env.RPC_URL); const app = Fastify(); // Cached endpoint for contract data app.get('/contract/:address/balance/:account', { config: { rateLimit: { max: 100, timeWindow: '1 minute' } } }, async (req, reply) => { const { address, account } = req.params as { address: string; account: string }; const cacheKey = `balance:${address}:${account}`; const cached = await redis.get(cacheKey); if (cached) return { balance: cached, cached: true }; const contract = new Contract(address, ERC20_ABI, provider); const balance = await contract.balanceOf(account); await redis.setex(cacheKey, 12, balance.toString()); // cache ~1 block (12 sec) return { balance: balance.toString(), cached: false }; }); Passwordless Secure Authentication Implementation
Sign-In With Ethereum (EIP-4361) is a passwordless authentication standard described in the EIP-4361 specification. The user signs a SIWE message, the backend verifies the signature and issues a JWT:
import { SiweMessage } from 'siwe'; import jwt from 'jsonwebtoken'; app.post('/auth/verify', async (req, reply) => { const { message, signature } = req.body; const siweMessage = new SiweMessage(message); const result = await siweMessage.verify({ signature }); if (!result.success) { return reply.code(401).send({ error: 'Invalid signature' }); } const token = jwt.sign( { address: result.data.address, chainId: result.data.chainId }, process.env.JWT_SECRET!, { expiresIn: '7d' } ); return { token }; }); Nonce to protect against replay attacks: we generate a random nonce, store it in Redis with a 5-minute TTL, and verify that the nonce in the SIWE message matches the one issued. After use, we delete it.
How to Set Up an RPC Proxy?
- Install Fastify and ethers.js:
npm install fastify ethers - Create a
server.tsfile and configure a rate limiter (e.g.,@fastify/rate-limit) - Connect Redis for caching: use the
ioredislibrary - Implement a fallback provider: switch to a backup RPC URL on error
- Add endpoints for reading data (balance, symbol, decimals) with caching
- Set up health checks and logging (pino)
How to Process On-Chain Events without Delays?
On-chain events are needed for displaying transaction history, notifications, analytics. Two approaches:
| Approach | Latency | RPC Load | Reliability |
|---|---|---|---|
| Polling | ~12-15 sec | High CUPS | Lower (block skipping) |
| WebSocket subscription | <1 sec | Low | Requires reconnect logic |
WebSocket subscription (the right way) — what we use in practice:
const wsProvider = new WebSocketProvider(process.env.WSS_RPC_URL); const contract = new Contract(CONTRACT_ADDRESS, ABI, wsProvider); contract.on('Transfer', async (from, to, value, event) => { await db.transfers.insert({ from, to, value: value.toString(), blockNumber: event.log.blockNumber, txHash: event.log.transactionHash, timestamp: new Date(), }); // Notify subscribers via WebSocket/SSE eventBus.emit('transfer', { from, to, value: value.toString() }); }); // Handle connection drops wsProvider.on('error', async () => { console.error('WS disconnected, reconnecting...'); setTimeout(setupSubscriptions, 5000); }); WebSocket connections are unstable — reconnect logic is mandatory. Alternatives for production: Alchemy webhooks, QuickNode Streams — the provider delivers events to your HTTP endpoint directly.
Gasless Transactions (Meta-Transactions)
EIP-2771 + ERC-2612 allow a user to sign a transaction off-chain, while a relayer pays the gas. The backend acts as a relayer, reducing user gas costs by 30-50%:
app.post('/relay/transfer', authenticateJWT, async (req, reply) => { const { permit, signature } = req.body; // ERC-2612 permit // Verify permit signature const tokenContract = new Contract(TOKEN_ADDRESS, ERC20_ABI, wallet); // Check permit is valid and not expired const nonce = await tokenContract.nonces(permit.owner); if (BigInt(permit.nonce) !== nonce) { return reply.code(400).send({ error: 'Invalid nonce' }); } // Execute permit + transferFrom on behalf of the user const tx = await tokenContract.permit( permit.owner, permit.spender, permit.value, permit.deadline, permit.v, permit.r, permit.s ); await tx.wait(); return { txHash: tx.hash }; }); For production gasless transactions: OpenZeppelin Defender Relayer or Biconomy — they manage nonces, retry logic, and monitoring of stuck transactions, ensuring 99.9% uptime.
How to Ensure RPC Proxy Fault Tolerance?
We use backup RPC providers with a circuit breaker. If the primary provider returns errors (e.g., 429 Too Many Requests), we automatically switch to the backup. Circuit breaker pattern via the opossum library: break after 5 consecutive errors for 30 seconds.
Monitoring and Reliability
Typical Issues and Their Solutions
-
Stuck transactions: a transaction with low gasPrice stalls in the mempool. We monitor via polling
getTransactionReceipt(). After N minutes, we bump gas (resend with same nonce, gasPrice * 1.1). - Nonce management: with parallel transactions from the same wallet, an atomic nonce counter is needed. We use Redis INCR + pending nonce tracking.
- Circuit breaker for RPC: if a provider returns errors, we switch to the backup.
Project Structure
src/ api/ # HTTP routes (Fastify/Express) blockchain/ # Provider, contracts, event listeners services/ # Business logic workers/ # BullMQ workers for background tasks db/ # Prisma schema, migrations cache/ # Redis client middleware/ # Auth, rate limiting, validation Fastify is about 15-20% faster than Express in throughput and has built-in JSON schema validation. For dApp backends, the difference is rarely critical, but the fastify-plugin ecosystem is convenient.
Deployment Strategy Comparison
| Strategy | Deployment Time | Fault Tolerance | Cost |
|---|---|---|---|
| Single server | 1-2 hours | Low | Low |
| Docker + compose | 3-4 hours | Medium | Medium |
| Kubernetes | 1-2 days | High | High |
What's Included in the Work
We deliver a full documentation package: API description (OpenAPI/Swagger), architectural diagram, deployment instructions. The source code is stored in a private repository with configured CI/CD (GitHub Actions). Access to RPC providers, Redis, and the database is transferred via a password manager. We conduct a team training session (1-2 hours). Support for 1 month after handover is included. Every project is delivered with a guaranteed security audit and 1-month free support. Request a dApp backend development quote—get a ready-made solution with monitoring and support.
Timeline Estimates
Base backend (RPC proxy + SIWE auth + caching): 2-3 days. Event indexer + WebSocket push + gasless relay: another 3-4 days. Production-ready with monitoring, retry logic, and fallback RPC: 1.5-2 weeks.
Contact us for an assessment of your project—we'll estimate the timeline and scope. Get an engineer's consultation.







