Разработка API для доступа к собранным данным

Проектируем и разрабатываем блокчейн-решения полного цикла: от архитектуры смарт-контрактов до запуска DeFi-протоколов, NFT-маркетплейсов и криптобирж. Аудит безопасности, токеномика, интеграция с существующей инфраструктурой.
Показано 1 из 1Все 1306 услуг
Разработка API для доступа к собранным данным
Средний
~3-5 дней
Часто задаваемые вопросы

Направления блокчейн-разработки

Этапы блокчейн-разработки

Последние работы

  • image_website-b2b-advance_0.webp
    Разработка сайта компании B2B ADVANCE
    1286
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1198
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    902
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1122
  • image_logo-advance_0.webp
    Разработка логотипа компании B2B Advance
    589
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    859

Разработка API для доступа к собранным данным

Собрать данные — это половина задачи. Вторая половина — сделать их доступными в формате, который не тормозит клиентов, не ложится под нагрузкой и не рассказывает всем подряд что вы парсили последние полгода. API для blockchain/crypto данных имеет специфику: данные immutable и append-only (исторические записи не меняются), объёмы большие (миллионы транзакций), запросы часто time-series по природе, клиенты хотят как REST для простых запросов так и WebSocket для real-time.

Дизайн REST API

Версионирование и URL структура

/v1/funding-rates/{symbol}?exchange=binance&from=2024-01-01&to=2024-03-01&limit=500
/v1/transactions/{chain}/{address}?from_block=19000000&limit=100
/v1/news?tags=bitcoin,regulation&from=2024-01-15T00:00:00Z&limit=50
/v1/gas/history?network=ethereum&granularity=1h&from=2024-01-01

Принципы:

  • limit + cursor пагинация вместо page + offset — стабильна при вставке новых данных
  • Временные параметры в ISO 8601 (включая timezone) или unix milliseconds — принимать оба
  • ?fields= для field selection — не отдавать 30 полей если клиент использует 3

Cursor-based пагинация

interface PaginatedResponse<T> {
  data: T[];
  pagination: {
    cursor: string | null;  // null = последняя страница
    hasMore: boolean;
    total?: number;         // опционально, дорого считать
  };
}

// Cursor = base64(JSON({lastId, lastTimestamp}))
// Запрос следующей страницы: GET /v1/funding-rates?cursor={opaque_string}

При cursor-пагинации добавление новых записей в начало не ломает навигацию клиента по истории.

Query параметры и валидация

Zod для валидации на входе — описывает схему и конвертирует типы:

import { z } from "zod";

const FundingRatesQuerySchema = z.object({
  symbol:   z.string().regex(/^[A-Z]+-[A-Z]+$/, "Invalid symbol format"),
  exchange: z.enum(["binance", "bybit", "okx", "hyperliquid"]).optional(),
  from:     z.coerce.date(),
  to:       z.coerce.date(),
  limit:    z.coerce.number().min(1).max(1000).default(100),
  cursor:   z.string().optional(),
});

type FundingRatesQuery = z.infer<typeof FundingRatesQuerySchema>;

Ранний возврат 400 с детальными ошибками экономит время клиентов:

{
  "error": "VALIDATION_ERROR",
  "details": [
    { "field": "from", "message": "Invalid date format" },
    { "field": "limit", "message": "Must be between 1 and 1000" }
  ]
}

Производительность: кеширование и оптимизация запросов

Многоуровневый кеш

Client → CDN (статичные исторические данные, TTL 1h)
       → Application cache (Redis, TTL 30s–5m)
       → Read replica PostgreSQL / ClickHouse

Redis кеширование по ключу запроса:

async function getFundingRates(query: FundingRatesQuery): Promise<FundingRateRecord[]> {
  const cacheKey = `fr:${query.symbol}:${query.exchange ?? "all"}:${query.from.getTime()}:${query.to.getTime()}`;
  
  const cached = await redis.get(cacheKey);
  if (cached) return JSON.parse(cached);
  
  const data = await db.queryFundingRates(query);
  
  // Исторические данные (прошлое) кешируем долго, real-time - кратко
  const ttl = query.to < new Date(Date.now() - 3600_000) ? 3600 : 30;
  await redis.setEx(cacheKey, ttl, JSON.stringify(data));
  
  return data;
}

Database query optimization

Для time-series данных в PostgreSQL — индексы критичны:

-- Покрывающий индекс для типичного запроса
CREATE INDEX CONCURRENTLY idx_funding_rates_lookup 
ON funding_rates (symbol, exchange, settled_at DESC)
INCLUDE (funding_rate, mark_price);

-- Запрос должен использовать этот индекс:
EXPLAIN ANALYZE
SELECT settled_at, funding_rate, mark_price
FROM funding_rates
WHERE symbol = 'BTC-USDT'
  AND exchange = 'binance'
  AND settled_at BETWEEN $1 AND $2
ORDER BY settled_at DESC
LIMIT 100;

Для аналитических запросов (агрегации, средние по периодам) — ClickHouse значительно быстрее PostgreSQL.

WebSocket для real-time данных

// Fastify + @fastify/websocket
fastify.get("/v1/stream", { websocket: true }, (socket, req) => {
  const subscriptions = parseSubscriptions(req.query);
  
  const unsubscribers = subscriptions.map((sub) =>
    eventBus.on(sub.channel, (data) => {
      if (socket.readyState === WebSocket.OPEN) {
        socket.send(JSON.stringify({ channel: sub.channel, data }));
      }
    })
  );
  
  socket.on("message", (msg) => {
    const cmd = JSON.parse(msg.toString());
    if (cmd.type === "subscribe") { /* ... */ }
    if (cmd.type === "unsubscribe") { /* ... */ }
    if (cmd.type === "ping") socket.send(JSON.stringify({ type: "pong" }));
  });
  
  socket.on("close", () => unsubscribers.forEach(unsub => unsub()));
});

Heartbeat: сервер шлёт ping каждые 30 секунд, клиент должен ответить pong. Если нет ответа за 10 секунд — закрываем соединение. Это отсекает зависшие соединения которые занимают memory.

Rate limiting и auth

API keys вместо JWT для server-to-server: не истекают, легко инвалидировать, не требуют refresh flow:

// Middleware
async function apiKeyAuth(req: FastifyRequest, reply: FastifyReply) {
  const key = req.headers["x-api-key"];
  if (!key) return reply.code(401).send({ error: "API key required" });
  
  const apiKey = await redis.hGetAll(`apikey:${key}`);
  if (!apiKey.id) return reply.code(401).send({ error: "Invalid API key" });
  
  req.apiKeyId = apiKey.id;
  req.rateLimitTier = apiKey.tier; // "free", "standard", "premium"
}

Sliding window rate limiting через Redis Lua script — атомарная операция:

local key = KEYS[1]
local limit = tonumber(ARGV[1])
local window = tonumber(ARGV[2])
local now = tonumber(ARGV[3])

redis.call("ZREMRANGEBYSCORE", key, 0, now - window)
local count = redis.call("ZCARD", key)
if count >= limit then return 0 end
redis.call("ZADD", key, now, now)
redis.call("EXPIRE", key, window / 1000)
return 1

Rate limit headers в ответах: X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset — стандартная практика, клиенты могут адаптировать своё поведение.

Observability

Structured logging каждого запроса с: api_key_id, endpoint, query_params (без секретов), response_time_ms, status_code, cache_hit.

Prometheus метрики:

const httpRequestDuration = new Histogram({
  name: "http_request_duration_ms",
  help: "HTTP request duration in milliseconds",
  labelNames: ["method", "route", "status_code"],
  buckets: [5, 10, 25, 50, 100, 250, 500, 1000, 2500],
});

P99 latency по каждому endpoint — главный SLA метрик. Alert если P99 > 500ms для cached запросов или > 2000ms для некешированных.

Стек: Fastify (Node.js) для REST + WebSocket, Redis для кеша и rate limiting, PostgreSQL/ClickHouse для данных, Prometheus + Grafana для мониторинга.

Срок разработки API поверх готовой БД с данными: 4–7 недель включая auth, rate limiting, документацию (OpenAPI spec).