{
"$type": "site.standard.document",
"bskyPostRef": {
"cid": "bafyreibvryoagxglsau7oefh6lua7utjnl4sybaqryqb2eenm6a7pvxazu",
"uri": "at://did:plc:25rdn5elo5izoxrmtis34zuk/app.bsky.feed.post/3monhtgnmet32"
},
"coverImage": {
"$type": "blob",
"ref": {
"$link": "bafkreiewnjkvxxfeiwuh3sz5i3crz54kehm5gr43tmz27jq3pwgpsf3z64"
},
"mimeType": "image/webp",
"size": 93904
},
"path": "/draphy/why-thousands-of-developers-are-mass-migrating-to-pushforge-for-web-push-notifications-2eeg",
"publishedAt": "2026-06-19T13:37:26.000Z",
"site": "https://dev.to",
"tags": [
"javascript",
"webdev",
"typescript",
"opensource",
"718",
"pushforge.draphy.org",
"github.com/draphy/pushforge",
"npmjs.com/package/@pushforge/builder",
"David Raphi",
"@pushforge",
"@types"
],
"textContent": "_\"crypto.createECDH is not a function\" - If you've seen this error, you're not alone. Here's why the web push landscape is finally changing._\n\nIf you've ever tried implementing push notifications on Cloudflare Workers, Vercel Edge, or any modern edge runtime, you've probably hit the same wall that thousands of developers face every month:\n\n\n\n TypeError: crypto.createECDH is not a function\n\n\nOr maybe this one:\n\n\n\n ReferenceError: https.request is not available in edge runtime\n\n\nThese errors aren't bugs in your code. They're symptoms of a fundamental problem: **the most popular web push library was built for a world that no longer exists**.\n\n## The Uncomfortable Truth About web-push\n\nFor years, `web-push` has been the de facto standard for sending push notifications in Node.js. With 18+ million monthly downloads, it's the default choice that tutorials recommend, Stack Overflow answers reference, and ChatGPT suggests.\n\nBut here's what those recommendations don't tell you:\n\nThe Promise | The Reality\n---|---\n\"Works everywhere\" | Fails on Cloudflare Workers (#718)\n\"Simple setup\" | Requires 5+ nested dependencies\n\"Production ready\" | Node.js deprecation warnings in v24\n\"Modern JavaScript\" | Uses legacy `crypto.createECDH` API\n\n**The issue has been open since 2022.** The workarounds are fragile. The compatibility flags are band-aids. And the edge computing revolution isn't waiting.\n\n## Enter PushForge: Built for Where You're Deploying Today\n\n**PushForge** is a zero-dependency web push library built entirely on the Web Crypto API, the same standard that runs in every modern browser, every edge runtime, and every JavaScript environment.\n\n\n\n import { buildPushHTTPRequest } from \"@pushforge/builder\";\n\n const { endpoint, headers, body } = await buildPushHTTPRequest({\n privateJWK: VAPID_PRIVATE_KEY,\n subscription: userSubscription,\n message: {\n payload: { title: \"Hello!\", body: \"This works everywhere.\" },\n adminContact: \"mailto:admin@example.com\"\n }\n });\n\n await fetch(endpoint, { method: \"POST\", headers, body });\n\n\nThat's it. No polyfills. No compatibility flags. No \"it works on my machine.\"\n\n## The Comparison Developers Are Making\n\nFeature | PushForge | web-push\n---|---|---\nDependencies | **0** | 5+ (with transitive deps)\nCloudflare Workers | **Yes** | No (#718)\nVercel Edge Functions | **Yes** | No\nConvex | **Yes** * | No\nDeno | **Yes** | Limited\nBun | **Yes** | Partial\nTypeScript | **Native** | @types package\nBundle Size | **~15KB** | ~150KB+ with deps\n\n_* Convex requires`\"use node\";` directive_\n\n## The Numbers: Organic Growth That Speaks Volumes\n\n**72,500+ monthly downloads** and growing. Zero paid marketing. Just developers solving real problems and telling others.\n\nPushForge launched in April 2025 and has seen consistent month-over-month growth as edge computing adoption accelerates. The trajectory tells you which direction the industry is moving.\n\n## Why PushForge Works Where web-push Fails\n\nThe difference comes down to one architectural decision: **standards vs. legacy APIs**.\n\n### web-push approach (Node.js specific):\n\n\n const crypto = require('crypto');\n const https = require('https');\n const ecdh = crypto.createECDH('prime256v1'); // Node.js only\n\n\n### PushForge approach (Web Standards):\n\n\n const keyPair = await crypto.subtle.generateKey(\n { name: 'ECDH', namedCurve: 'P-256' },\n true, ['deriveBits']\n ); // Works everywhere\n\n\nThe Web Crypto API is:\n\n * Available in every modern browser\n * Supported by Cloudflare Workers, Vercel Edge, Deno, Bun\n * Part of Node.js 20+ without flags\n * The W3C standard for cryptographic operations\n\n\n\n**PushForge doesn't \"polyfill\" or \"shim\" its way to compatibility. It's built on the foundation that every JavaScript runtime shares.**\n\n## Try Before You Trust: The Interactive Playground\n\nDon't take our word for it. **Test PushForge in your browser right now:**\n\n**pushforge.draphy.org**\n\nThe playground lets you:\n\n * **Quick Test** : Enable notifications, send a test message, see it arrive\n * **Topic Channels** : Subscribe to named topics, target specific groups\n * **Full Customization** : Icons, images, action buttons, vibration patterns\n * **Push Options** : Test urgency levels (battery hints), TTL, topic replacement\n * **Cross-Browser** : Chrome, Firefox, Safari 16+, Edge, Brave\n\n\n\nThe backend? A single Cloudflare Worker using `buildPushHTTPRequest()`. Zero dependencies. The same code you'd write.\n\nSubscriptions auto-expire (5 minutes for quick test, 1 hour for topics). No accounts. No data stored. Just push notifications, working.\n\n## Getting Started in 60 Seconds\n\n### 1. Install\n\n\n npm install @pushforge/builder\n\n\n### 2. Generate VAPID Keys\n\n\n npx @pushforge/builder vapid\n\n\nSave the output: public key for your frontend, private key (JWK) for your server.\n\n### 3. Subscribe Users (Frontend)\n\n\n const registration = await navigator.serviceWorker.ready;\n const subscription = await registration.pushManager.subscribe({\n userVisibleOnly: true,\n applicationServerKey: VAPID_PUBLIC_KEY\n });\n // Send subscription to your server\n\n\n### 4. Send Notifications (Server)\n\n\n import { buildPushHTTPRequest } from \"@pushforge/builder\";\n\n const { endpoint, headers, body } = await buildPushHTTPRequest({\n privateJWK: JSON.parse(process.env.VAPID_PRIVATE_KEY),\n subscription,\n message: {\n payload: {\n title: \"New Message\",\n body: \"You have a notification!\",\n icon: \"/icon.png\",\n data: { url: \"/messages\" }\n },\n adminContact: \"mailto:you@example.com\",\n options: {\n ttl: 3600,\n urgency: \"high\"\n }\n }\n });\n\n const response = await fetch(endpoint, { method: \"POST\", headers, body });\n\n\n### Platform Examples Ready to Copy\n\n**Cloudflare Workers:**\n\n\n\n export default {\n async fetch(request, env) {\n const { endpoint, headers, body } = await buildPushHTTPRequest({\n privateJWK: JSON.parse(env.VAPID_PRIVATE_KEY),\n subscription: await request.json(),\n message: {\n payload: { title: \"Hello from the Edge!\" },\n adminContact: \"mailto:admin@example.com\"\n }\n });\n return fetch(endpoint, { method: \"POST\", headers, body });\n }\n };\n\n\n**Vercel Edge Functions:**\n\n\n\n export const config = { runtime: \"edge\" };\n\n export default async function handler(request: Request) {\n const { endpoint, headers, body } = await buildPushHTTPRequest({\n privateJWK: JSON.parse(process.env.VAPID_PRIVATE_KEY!),\n subscription: await request.json(),\n message: {\n payload: { title: \"Edge Notification\" },\n adminContact: \"mailto:admin@example.com\"\n }\n });\n await fetch(endpoint, { method: \"POST\", headers, body });\n return new Response(\"Sent\");\n }\n\n\n**Deno:**\n\n\n\n import { buildPushHTTPRequest } from \"npm:@pushforge/builder\";\n\n const { endpoint, headers, body } = await buildPushHTTPRequest({\n privateJWK: JSON.parse(Deno.env.get(\"VAPID_PRIVATE_KEY\")!),\n subscription,\n message: {\n payload: { title: \"Hello from Deno!\" },\n adminContact: \"mailto:admin@example.com\"\n }\n });\n\n await fetch(endpoint, { method: \"POST\", headers, body });\n\n\n## What Makes PushForge Different\n\n### TypeScript-First, Not TypeScript-Added\n\nPushForge isn't a JavaScript library with type definitions bolted on. It's written in TypeScript from the ground up:\n\n\n\n import type {\n BuilderOptions,\n PushMessage,\n PushSubscription\n } from \"@pushforge/builder\";\n\n\nYour IDE knows the shape of every parameter. Your compiler catches mistakes before runtime. Your code documents itself.\n\n### Security Built In, Not Bolted On\n\nPushForge validates everything before processing:\n\n * VAPID key structure (EC P-256 curve with required x, y, d parameters)\n * Subscription endpoints (must be valid HTTPS URLs)\n * p256dh keys (65-byte uncompressed P-256 point format)\n * Auth secrets (exactly 16 bytes)\n * Payload size (max 4KB per Web Push spec)\n * TTL bounds (max 24 hours per VAPID spec)\n\n\n\nInvalid input fails fast with clear error messages, not cryptic failures deep in the crypto stack.\n\n## The Roadmap: What's Coming\n\nPushForge is actively developed. Here's what's on the horizon:\n\n * **Batching & Queuing**: Bulk notification delivery with rate limiting\n * **Built-in Retry Logic** : Automatic error handling for push service failures\n * **Framework Examples** : Ready-to-use templates for React, Vue, Next.js, SvelteKit\n * **Service Worker Templates** : Drop-in notification handling code\n\n\n\nFollow development: github.com/draphy/pushforge\n\n## The Developer Community\n\nPushForge welcomes contributions. The workflow is straightforward:\n\n 1. Open an issue describing your change\n 2. Fork the repository\n 3. Create a branch: `username/wpn-issuenumber-description`\n 4. Follow conventional commit guidelines\n 5. Submit a pull request\n\n\n\nThe codebase uses modern tooling:\n\n * **Biome** for fast formatting and linting\n * **Vitest** for comprehensive testing\n * **Semantic Release** for automated versioning\n * **GitHub Actions** for CI/CD\n\n\n\n## Why Developers Are Switching\n\nHere's what the migration typically looks like:\n\n**Before (web-push):**\n\n\n\n const webpush = require('web-push');\n\n webpush.setVapidDetails(\n 'mailto:admin@example.com',\n publicKey,\n privateKey\n );\n\n // Hope you're not on Cloudflare Workers...\n await webpush.sendNotification(subscription, payload);\n\n\n**After (PushForge):**\n\n\n\n import { buildPushHTTPRequest } from \"@pushforge/builder\";\n\n const { endpoint, headers, body } = await buildPushHTTPRequest({\n privateJWK,\n subscription,\n message: { payload, adminContact: \"mailto:admin@example.com\" }\n });\n\n // Works on ANY runtime with fetch()\n await fetch(endpoint, { method: \"POST\", headers, body });\n\n\nThe API is intentionally minimal. You get `endpoint`, `headers`, and `body`. You use `fetch()`. That's the entire interface, and it works everywhere `fetch()` works.\n\n## The Bottom Line\n\nThe web has evolved. Serverless is the default. Edge computing is mainstream. The tools should match the territory.\n\n**PushForge is:**\n\n * Zero dependencies (no supply chain surprises)\n * Web standards based (no Node.js lock-in)\n * TypeScript native (no type guessing)\n * Edge ready (Cloudflare, Vercel, Deno, Bun - all first-class)\n * Battle tested (70,000+ monthly downloads and growing)\n * Open source (MIT licensed, transparent development)\n\n\n\n**web-push served its era well.** But if you're deploying to modern infrastructure, you deserve a modern foundation.\n\n## Get Started Now\n\n\n npm install @pushforge/builder\n\n\n * **Documentation** : github.com/draphy/pushforge\n * **npm** : npmjs.com/package/@pushforge/builder\n * **Playground** : pushforge.draphy.org\n\n\n\n_PushForge is MIT licensed and open source. Created by David Raphi._\n\n_Have questions? Open an issue. Found a bug? PRs welcome. Building something cool? I'd love to hear about it._",
"title": "Why Thousands of Developers Are Mass Migrating to PushForge for Web Push Notifications"
}