Разработка бэкенда dApp на Node.js

Проектируем и разрабатываем блокчейн-решения полного цикла: от архитектуры смарт-контрактов до запуска DeFi-протоколов, NFT-маркетплейсов и криптобирж. Аудит безопасности, токеномика, интеграция с существующей инфраструктурой.
Показано 1 из 1 услугВсе 1306 услуг
Разработка бэкенда dApp на Node.js
Средняя
~1-2 недели
Часто задаваемые вопросы
Направления блокчейн-разработки
Этапы блокчейн-разработки
Последние работы
  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1258
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1170
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    873
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1092
  • image_logo-advance_0.png
    Разработка логотипа компании B2B Advance
    563
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    830

Разработка бэкенда dApp на Node.js

Бэкенд нужен не всем dApp — это первое, что нужно понять. Если логика полностью on-chain, а фронтенд читает данные через публичный RPC — бэкенд избыточен. Но как только появляются: приватные API ключи, агрегация данных с нескольких источников, кэширование дорогих on-chain запросов, gasless транзакции (relayer), верификация подписей — без бэкенда не обойтись.

Ключевые компоненты

RPC abstraction layer

Прямое обращение из фронтенда к Infura/Alchemy раскрывает API ключ. Бэкенд проксирует RPC вызовы, добавляет кэширование и rate limiting:

import { JsonRpcProvider, Contract } from 'ethers';
import Fastify from 'fastify';

const provider = new JsonRpcProvider(process.env.RPC_URL);

const app = Fastify();

// Кэшированный endpoint для данных контракта
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()); // кэш на ~1 блок (12 сек)
  return { balance: balance.toString(), cached: false };
});

Верификация подписей (authentication)

Sign-In With Ethereum (EIP-4361) — стандарт аутентификации без пароля. Пользователь подписывает SIWE-сообщение, бэкенд верифицирует подпись и выдаёт 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 для защиты от replay атак: генерируем случайный nonce, сохраняем в Redis с TTL 5 минут, верифицируем что nonce в SIWE сообщении совпадает с выданным. После использования — удаляем.

Event indexing

On-chain события нужны для отображения истории операций, уведомлений, аналитики. Два подхода:

Polling (простой): каждые N секунд опрашиваем getLogs за последние блоки. Работает, но с задержкой и расходует CUPS провайдера.

WebSocket subscription (правильный):

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(),
  });
  
  // Уведомить подписчиков через WebSocket/SSE
  eventBus.emit('transfer', { from, to, value: value.toString() });
});

// Обработка разрыва соединения
wsProvider.on('error', async () => {
  console.error('WS disconnected, reconnecting...');
  setTimeout(setupSubscriptions, 5000);
});

WebSocket соединения нестабильны — reconnect логика обязательна. Алернатива для production: Alchemy webhooks, Quicknode Streams — провайдер сам доставляет события на ваш HTTP endpoint.

Gasless транзакции (meta-transactions)

EIP-2771 + ERC-2612 позволяют пользователю подписывать транзакцию офф-чейн, а relayer оплачивает газ. Бэкенд работает как relayer:

app.post('/relay/transfer', authenticateJWT, async (req, reply) => {
  const { permit, signature } = req.body; // ERC-2612 permit
  
  // Верифицируем permit signature
  const tokenContract = new Contract(TOKEN_ADDRESS, ERC20_ABI, wallet);
  
  // Проверяем что permit валиден и не истёк
  const nonce = await tokenContract.nonces(permit.owner);
  if (BigInt(permit.nonce) !== nonce) {
    return reply.code(400).send({ error: 'Invalid nonce' });
  }
  
  // Выполняем permit + transferFrom за пользователя
  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 };
});

Для production gasless транзакций: OpenZeppelin Defender Relayer или Biconomy — они управляют nonce, retry logic и мониторингом застрявших транзакций.

Структура проекта

src/
  api/          # HTTP routes (Fastify/Express)
  blockchain/   # Provider, contracts, event listeners
  services/     # Бизнес-логика
  workers/      # BullMQ workers для фоновых задач
  db/           # Prisma schema, migrations
  cache/        # Redis client
  middleware/   # Auth, rate limiting, validation

Fastify быстрее Express на ~15-20% throughput и имеет встроенную JSON schema валидацию. Для dApp бэкенда разница редко критична, но fastify-plugin экосистема удобна.

Мониторинг и надёжность

Stuck transactions: транзакция с низким gasPrice зависает в mempool. Мониторим через polling getTransactionReceipt(). После N минут — bump gas (resend с тем же nonce, gasPrice * 1.1).

Nonce management: при параллельных транзакциях с одного кошелька нужен атомарный nonce counter. Redis INCR + pending nonce tracking, или библиотека типа ethers-multicall.

Circuit breaker для RPC: если провайдер возвращает ошибки — переключаемся на резервный. Паттерн circuit breaker через opossum или вручную.

Ориентиры по срокам

Базовый бэкенд (RPC proxy + SIWE auth + кэширование) — 2-3 дня. Event indexer + WebSocket push + gasless relay — ещё 3-4 дня. Production-ready с мониторингом, retry logic и fallback RPC — 1.5-2 недели.