Building a Lightweight Behance Metadata Proxy with Rate‑Limit Awareness
Learn how to design a secure, read‑only backend that fetches Behance project metadata, caches results, and enforces API limits while keeping the API key hidden.
25 Aug 2026, 08:44 UTC

Requirements
To expose Behance project metadata to a client application you need a backend that:
- Calls
https://api.behance.net/v2/projects/{id}with a valid API key. - Returns only the fields the client needs (e.g.,
title,description,images,tags). - Respects Behance’s rate limit of 100 requests per minute per key.
- Secures the API key so it never reaches the browser.
- Caches responses to reduce latency and avoid hitting the limit.
- Provides health‑check endpoints and structured logs for ops.
Smallest Suitable Design
The core of the service is a single Express route: /project/:id. Behind the scenes we use axios for HTTP, node-cache for in‑memory TTL caching, and bottleneck for rate limiting. The following diagram shows the flow.
Architecture Diagram
┌─────────────────────┐
│ Client Application │
└───────▲───────┬──────┘
│ │
│ ▼
┌─────────────────────┐
│ Behance Metadata │
│ Proxy Service │
│ (Node.js / Express)│
└───────▲───────┬──────┘
│ │
│ ▼
┌─────────────────────┐
│ Behance Public API │
└─────────────────────┘
Key components:
- API Key Storage: Environment variable
BEHANCE_API_KEYloaded viadotenv. Never commit the key to source control. - Rate Limiter:
bottleneckconfigured to 100 jobs per minute. If the queue is full, the request is delayed automatically. - Cache Layer:
node-cachewith a TTL of 10 minutes. Cached entries are keyed by project ID. - Client Endpoint:
/project/:idreturns sanitized JSON.
Sample Implementation
require('dotenv').config();
const express = require('express');
const axios = require('axios');
const NodeCache = require('node-cache');
const Bottleneck = require('bottleneck');
const app = express();
const cache = new NodeCache({ stdTTL: 600 }); // 10 min TTL
const limiter = new Bottleneck({
minTime: 600, // 100 req/min
maxConcurrent: 1
});
const BEHANCE_API_KEY = process.env.BEHANCE_API_KEY;
if (!BEHANCE_API_KEY) {
throw new Error('Missing BEHANCE_API_KEY');
}
async function fetchProject(id) {
const url = `https://api.behance.net/v2/projects/${id}?api_key=${BEHANCE_API_KEY}`;
const response = await axios.get(url, { timeout: 5000 });
return response.data;
}
app.get('/project/:id', async (req, res) => {
const id = req.params.id;
const cached = cache.get(id);
if (cached) {
return res.json(cached);
}
try {
const data = await limiter.schedule(() => fetchProject(id));
const sanitized = {
title: data.title,
description: data.description,
images: data.images,
tags: data.tags
};
cache.set(id, sanitized);
res.json(sanitized);
} catch (err) {
if (err.response && err.response.status === 429) {
res.status(503).json({ error: 'Rate limit exceeded, try again later.' });
} else {
console.error(err);
res.status(502).json({ error: 'Failed to fetch project data.' });
}
}
});
app.get('/health', (req, res) => res.json({ status: 'ok' }));
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => console.log(`Server listening on ${PORT}`));
Trust & Data Boundaries
The service is strictly read‑only. It never stores or logs the API key, project descriptions, or any user‑generated content beyond the HTTP response. All data that leaves the service is the sanitized subset required by the client. This keeps the trust boundary tight: the client never sees the raw Behance API response, and the proxy never has access to private projects or OAuth tokens.
Operational Checks
- Health‑Check Endpoint:
/healthreturns{ status: 'ok' }to allow load balancers and monitoring tools to verify uptime. - Structured Logging: Use
pinoorwinstonto log request IDs, cache hits, and error details. Example log entry:{"level":30,"time":169714...,"msg":"Cache miss for project 12345","pid":1234,"hostname":"proxy1"} - Metrics: Expose Prometheus metrics for cache hit ratio, request latency, and 429 counts. This helps detect when the limit is being approached.
- Retry Strategy:
bottleneckautomatically queues requests. For transient network errors,axios-retrycan be added with exponential back‑off.
Failure Modes & Mitigations
- Rate‑Limit Exceeded (429)
- Behaviour: Service returns HTTP 503 with a user‑friendly message.
- Mitigation:
bottleneckkeeps the queue within limits; if the queue is full, requests are delayed instead of rejected.
- Behance API Downtime
- Behaviour:
axiosthrows a network error. - Mitigation: Retry with back‑off; after 3 attempts, return 502 to the client.
- Behaviour:
- Cache Miss / Stale Data
- Behaviour: Fresh API call is made; if the project changes, the cache will be updated on next request.
- Mitigation: TTL of 10 minutes balances freshness and load. If real‑time accuracy is critical, expose a manual invalidation endpoint or use a webhook.
- API Key Compromise
- Behaviour: Abuse of the key could trigger the 429 limit quickly.
- Mitigation: Rotate the key regularly, monitor usage patterns, and set up alerts for abnormal request rates.
- Schema Changes
- Behaviour: New fields appear or existing ones are renamed.
- Mitigation: Run a nightly health check that fetches a sample project; if the response shape changes, alert the dev team.
When to Change the Design
- Higher Throughput Needed: If the client demands >100 req/min, switch from
node-cacheto Redis and use a distributed rate limiter. - Private Project Access: Add OAuth 2.0 flow and token storage; the service must then handle token refreshes.
- Long‑Term Persistence: Store project snapshots in a database for audit or offline analysis.
- Multi‑Region Deployment: Deploy the proxy in multiple regions and use a global load balancer; each instance needs its own rate limiter.
By keeping the architecture minimal—one endpoint, in‑memory cache, and built‑in rate limiting—you can quickly iterate while ensuring the Behance API key remains secure and the service stays within limits. The design is intentionally simple, but the components are modular enough to evolve as requirements change.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.