A stateless HTTP API that turns chart and diagram specs into PNG, SVG, or PDF. One endpoint. Four renderers (Chart.js, Mermaid, Graphviz, Vega-Lite). Any chart.
- Production: https://api.chartforge.dev
- Stack: Node 20 + TypeScript + Fastify · Neon Postgres · Cloudflare R2 cache · Fly.io
Node 20 is required (canvas@3 has no prebuilt binaries past Node 20 ABI). Use fnm to get the right version automatically via the committed .node-version file.
# one-time: install Node 20 and system deps for the canvas native module
brew install fnm pkg-config pixman cairo pango librsvg giflib
fnm install 20
eval "$(fnm env --use-on-cd)" # add to ~/.zshrc for auto-switch on cd
# project setup
cp .env.example .env # fill in DATABASE_URL (Neon)
pnpm install
pnpm migrate # runs migrations/0001_init.sql
pnpm seed you@example.com pro # creates a user + prints a cf_test_ key (shown once)
pnpm dev # Fastify on :8080Then curl -H "Authorization: Bearer <the key>" http://127.0.0.1:8080/v1/me.
-- 1) Create a user
INSERT INTO users (id, email, plan) VALUES (gen_random_uuid(), 'you@example.com', 'pro');
-- 2) Generate a token outside psql: node -e "console.log('cf_test_' + require('crypto').randomBytes(24).toString('hex'))"
-- 3) SHA-256 hash it: echo -n 'cf_test_...' | shasum -a 256
-- 4) Insert it:
INSERT INTO api_keys (user_id, key_hash, prefix, name)
VALUES ((SELECT id FROM users WHERE email='you@example.com'), '<sha256>', 'cf_test', 'local-dev');# Chart.js bar chart -> PNG
curl -X POST https://api.chartforge.dev/v1/render/chart \
-H "Authorization: Bearer cf_live_yourkey" \
-H "Content-Type: application/json" \
-d '{
"type": "bar",
"data": {
"labels": ["Jan","Feb","Mar"],
"datasets": [{"label":"MRR","data":[1200,1900,2400]}]
},
"width": 800, "height": 500, "format": "png"
}' --output chart.png
# Mermaid via GET (drop straight into Markdown)
curl "https://api.chartforge.dev/v1/render/mermaid?diagram=graph%20TD%3B%20A--%3EB&format=png&key=cf_live_yourkey" \
--output flow.png
# Quota state
curl https://api.chartforge.dev/v1/me -H "Authorization: Bearer cf_live_yourkey"- Cache: SHA-256 of
{renderer, canonical_input, format, w, h}→ R2. All responses are deterministic, so the cache hit ratio climbs fast. Cache hits do NOT count against the monthly quota (enforced insrc/routes/render.ts). - Auth: API key SHA-256 hash stored in Postgres; raw key never persisted. Keys use the
cf_live_/cf_test_prefix pattern. - Rate limit: in-memory token bucket keyed on API key ID. Swap for Redis when scaling past one machine.
- Renderers: Chart.js is MVP-complete. Mermaid, Graphviz, and Vega are stubbed with install-and-implement instructions inline in each renderer file.
| Tier | Price | Renders/mo | Burst |
|---|---|---|---|
| Free | $0 | 2,500 | 5 req/s |
| Hobby | $19 | 25,000 | 30 req/s |
| Pro | $79 | 250,000 | 100 req/s |
| Scale | $299 | 2,000,000 | 500 req/s |
Cached renders don't count against quota on any tier.
pnpm testfly launch --no-deploy --copy-config
fly secrets set DATABASE_URL=... R2_ACCOUNT_ID=... R2_ACCESS_KEY_ID=... R2_SECRET_ACCESS_KEY=... R2_BUCKET=chartforge-cache STRIPE_SECRET_KEY=... STRIPE_WEBHOOK_SECRET=...
fly deployFirst build is slow (~5 min) due to node-canvas native compilation. Subsequent builds reuse the cached deps layer.
- Week 1 — Chart.js renderer live, auth, quota, Stripe, Fly deploy
- Week 2 — Mermaid (Playwright + warm Chromium), Graphviz (wasm), Vega (native), playground UI
- Week 3 — MCP server, LangChain/LlamaIndex/CrewAI tools, Node/Python/Go SDKs
- Week 4 — Public launch: Product Hunt Wednesday, Show HN the following Tuesday