Best npm Packages for Edge Runtimes in 2026
Not every npm package works on edge runtimes. Edge environments use the Fetch API instead of Node.js's http module, lack access to the filesystem, and have CPU time limits (10-50ms on Cloudflare Workers). The packages that work best are those built on Web Standards — and in 2026, that list has grown substantially as npm package authors have added edge compatibility.
TL;DR
The edge runtime npm ecosystem in 2026 centers on a few key categories: routing (Hono — 2M downloads, works on 9+ runtimes), storage (Cloudflare's D1, KV, R2 client libraries), validation (Zod works edge-natively, so does Valibot), and utilities (ioredis has alternatives, most date libraries work). Avoid packages that require fs, child_process, net, or Node.js-only streams — they'll fail at the edge.
Key Takeaways
- Hono: 2M weekly downloads, <14 kB, the standard router for edge runtimes
wrangler: Cloudflare's CLI + local emulation for D1, KV, R2, Durable Objects@cloudflare/workers-types: TypeScript types for Workers APIs (KV, D1, R2, etc.)@cloudflare/vitest-pool-workers: Run tests inside the actual Workers runtime- Zod: Fully edge-compatible (no Node.js dependencies)
- Packages to avoid:
express,axios(use native fetch),fs-extra,bcrypt(use SubtleCrypto) - Web Crypto API: Use
crypto.subtleinstead of Node.jscryptomodule
The Edge Runtime Constraint
Edge runtimes implement the WinterCG spec — a subset of Web APIs without Node.js-specific APIs:
// Available in edge runtimes:
fetch() // HTTP requests
Request // Web Fetch API
Response // Web Fetch API
URL // URL parsing
URLSearchParams // Query string
Headers // HTTP headers
crypto.subtle // Web Crypto (NOT Node.js crypto)
TextEncoder // Text encoding
TextDecoder // Text decoding
ReadableStream // Streaming
// NOT available in edge runtimes:
fs // No filesystem
child_process // No process spawning
net // No raw TCP
node:crypto // No Node.js crypto (use crypto.subtle)
Buffer // Limited (polyfill available in Workers)
Routing: Hono
Package: hono
Weekly downloads: 2M
GitHub stars: 23K
Hono is the standard framework for edge runtimes. It's built on Web Standards, has zero platform-specific dependencies, and works on Cloudflare Workers, Deno Deploy, Vercel Edge, AWS Lambda, Bun, and Node.js.
npm install hono
// Works identically on Cloudflare Workers, Deno Deploy, and Node.js
import { Hono } from 'hono';
import { cors } from 'hono/cors';
import { logger } from 'hono/logger';
import { validator } from 'hono/validator';
import { zValidator } from '@hono/zod-validator';
import { z } from 'zod';
const app = new Hono<{ Bindings: Env }>();
app.use('*', cors());
app.use('*', logger());
const userSchema = z.object({
name: z.string().min(2),
email: z.string().email(),
});
app.post('/api/users',
zValidator('json', userSchema),
async (c) => {
const data = c.req.valid('json');
const { meta } = await c.env.DB
.prepare('INSERT INTO users (name, email) VALUES (?, ?)')
.bind(data.name, data.email)
.run();
return c.json({ id: meta.last_row_id }, 201);
}
);
export default app;
Cloudflare Platform Packages
wrangler — The Essential CLI
npm install -D wrangler
# Local development with D1, KV, R2 emulation
wrangler dev
# Deploy to Cloudflare Workers
wrangler deploy
# Manage KV namespaces
wrangler kv namespace create CACHE
wrangler kv key put --binding=CACHE "key" "value"
# Create D1 database
wrangler d1 create my-database
wrangler d1 execute my-database --file=schema.sql
# Tail real-time logs from production
wrangler tail
# Generate TypeScript types from wrangler.toml
wrangler types
@cloudflare/workers-types — TypeScript Types
npm install -D @cloudflare/workers-types
// tsconfig.json
{
"compilerOptions": {
"types": ["@cloudflare/workers-types"]
}
}
// Or use wrangler types (preferred in 2026):
// Creates worker-configuration.d.ts with your specific bindings
// Now TypeScript knows about all Workers APIs:
async function handler(request: Request, env: Env): Promise<Response> {
// env.KV is typed as KVNamespace
const value = await env.KV.get('my-key');
// env.DB is typed as D1Database
const users = await env.DB.prepare('SELECT * FROM users').all();
// env.BUCKET is typed as R2Bucket
const object = await env.BUCKET.get('image.png');
return Response.json(users.results);
}
@cloudflare/vitest-pool-workers — Test in the Real Runtime
npm install -D @cloudflare/vitest-pool-workers vitest
// vitest.config.ts
import { defineWorkersConfig } from '@cloudflare/vitest-pool-workers/config';
export default defineWorkersConfig({
test: {
poolOptions: {
workers: {
wrangler: { configPath: './wrangler.toml' },
},
},
},
});
// api.test.ts — runs inside the actual Workers runtime
import { SELF } from 'cloudflare:test';
import { describe, it, expect } from 'vitest';
describe('API', () => {
it('returns users', async () => {
// SELF is your Worker — real D1, KV, R2 bindings
const response = await SELF.fetch('http://localhost/api/users');
expect(response.status).toBe(200);
const users = await response.json();
expect(Array.isArray(users)).toBe(true);
});
});
Tests run in the actual Workers runtime with real bindings — not a Node.js simulation.
@cloudflare/d1 and Database Patterns
// D1 (SQLite at the edge) — no separate client package needed
// Access via the env binding:
interface Env {
DB: D1Database;
}
export default {
async fetch(request: Request, env: Env) {
// Query
const users = await env.DB
.prepare('SELECT * FROM users WHERE active = ?')
.bind(true)
.all();
// Insert
const { meta } = await env.DB
.prepare('INSERT INTO users (name, email) VALUES (?, ?)')
.bind('Alice', 'alice@example.com')
.run();
// Batch queries
const results = await env.DB.batch([
env.DB.prepare('INSERT INTO users (name) VALUES (?)').bind('Alice'),
env.DB.prepare('INSERT INTO users (name) VALUES (?)').bind('Bob'),
]);
return Response.json({ users: users.results });
},
};
Edge-Compatible Libraries
Validation: Zod and Valibot
npm install zod
# or:
npm install valibot # Smaller bundle, fully edge-compatible
Both work natively in edge runtimes — no Node.js dependencies.
HTTP Client: Native Fetch
// DON'T use axios in edge runtimes (node http module dependency)
// import axios from 'axios'; // May fail on Workers
// DO use native fetch — available in all edge runtimes:
const response = await fetch('https://api.example.com/data', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ key: 'value' }),
});
const data = await response.json();
Cryptography: Web Crypto API
// DON'T use Node.js crypto:
// import crypto from 'crypto'; // Not available on edge
// DO use Web Crypto:
async function hashPassword(password: string): Promise<string> {
const encoder = new TextEncoder();
const data = encoder.encode(password);
const hash = await crypto.subtle.digest('SHA-256', data);
return btoa(String.fromCharCode(...new Uint8Array(hash)));
}
// JWT verification with Web Crypto:
async function verifyJWT(token: string, secret: string): Promise<boolean> {
const key = await crypto.subtle.importKey(
'raw',
new TextEncoder().encode(secret),
{ name: 'HMAC', hash: 'SHA-256' },
false,
['verify']
);
// ... verify signature
}
ORM: Drizzle with D1
npm install drizzle-orm
npm install -D drizzle-kit
// schema.ts
import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core';
export const users = sqliteTable('users', {
id: integer('id').primaryKey({ autoIncrement: true }),
name: text('name').notNull(),
email: text('email').notNull().unique(),
});
// index.ts
import { drizzle } from 'drizzle-orm/d1';
import { users } from './schema';
import { eq } from 'drizzle-orm';
export default {
async fetch(request: Request, env: Env) {
const db = drizzle(env.DB);
const allUsers = await db.select().from(users);
const user = await db.select()
.from(users)
.where(eq(users.email, 'alice@example.com'))
.get();
return Response.json(allUsers);
}
};
Date Libraries
npm install date-fns # Fully edge-compatible
// date-fns works in edge runtimes (no Node.js dependencies)
import { format, addDays, differenceInDays } from 'date-fns';
const formatted = format(new Date(), 'yyyy-MM-dd');
What NOT to Use in Edge Runtimes
// ❌ Node.js http module — use fetch instead
import http from 'http';
// ❌ Express — not edge-compatible
import express from 'express';
// ❌ Axios — depends on Node.js http (though newer versions have fetch mode)
import axios from 'axios';
// ❌ bcrypt — uses native bindings
import bcrypt from 'bcrypt';
// ✅ Use: bcryptjs (pure JS) or Web Crypto
// ❌ sharp — requires native bindings
import sharp from 'sharp';
// ✅ Use: Cloudflare Images API or a separate service
// ❌ node:crypto (import via node: protocol)
import crypto from 'node:crypto';
// ✅ Use: crypto.subtle (Web Crypto API)
// ❌ fs — no filesystem in edge runtimes
import fs from 'fs';
// ✅ Use: R2 for file storage, KV for small data
Edge Compatibility Checklist
Before using any npm package on an edge runtime:
# Check if package has "edge" in keywords or README
# Check for Node.js-specific imports in source
# Common red flags:
# - import { createServer } from 'http'
# - import { readFile } from 'fs'
# - import { exec } from 'child_process'
# - Mentions "Node.js required" in README
# Good signs:
# - "Works with Cloudflare Workers" in README
# - Uses only Web Standards APIs
# - Zero dependencies or all web-standard deps
# - "edge-runtime" package.json exports condition
The 2026 Edge Stack
// wrangler.toml
name = "my-api"
main = "src/index.ts"
compatibility_date = "2024-01-01"
compatibility_flags = ["nodejs_compat"] // Enable Node.js compat
[[d1_databases]]
binding = "DB"
database_name = "my-database"
database_id = "..."
[[kv_namespaces]]
binding = "CACHE"
id = "..."
[[r2_buckets]]
binding = "BUCKET"
bucket_name = "my-assets"
// package.json
{
"dependencies": {
"hono": "^4.x", // Routing
"drizzle-orm": "^0.x", // ORM for D1
"zod": "^3.x", // Validation
"@hono/zod-validator": "^0.x" // Hono + Zod integration
},
"devDependencies": {
"wrangler": "^3.x",
"@cloudflare/workers-types": "^4.x",
"@cloudflare/vitest-pool-workers": "^0.x",
"drizzle-kit": "^0.x",
"vitest": "^2.x"
}
}
Production Debugging and Observability at the Edge
Debugging edge runtime failures requires different tooling than traditional Node.js debugging. Cloudflare Workers' wrangler tail streams real-time logs from your production deployment, including request metadata and uncaught exceptions, but the logs are ephemeral — there is no persistent log storage by default. Integrate with a logging service by sending structured log events to a OTLP-compatible endpoint via fetch inside your worker, since file system logging and traditional syslog approaches are unavailable. For distributed tracing, use the cf-ray header that Cloudflare attaches to every request as a trace ID to correlate logs across Workers invocations, Durable Objects, and external service calls. Baselime and Highlight.io both offer edge-native observability that understands Workers' execution model rather than treating it as a Node.js process.
Cold Start Optimization
Cold starts are the primary latency concern for edge deployments. Workers' v8 isolate model is faster than Lambda's container cold starts — typically 0–5ms versus 100–500ms — but the isolate must still initialize your JavaScript module graph on first invocation. Bundle size directly affects cold start time: a 1 MB bundled worker takes longer to parse and compile than a 100 KB worker. Use Wrangler's --minify flag and tree-shaking to reduce bundle size, and avoid importing large libraries if you use only a small portion of their API surface. The nodejs_compat compatibility flag in wrangler.toml adds Node.js API polyfills that increase the bundle size — enable it only for specific APIs you actually need rather than using it as a blanket compatibility shim.
Security Considerations for Edge Deployments
Edge runtimes expose security considerations that differ from traditional server deployments. Environment variables set via wrangler secret put are encrypted at rest and injected into the worker at runtime as bindings — never hardcode secrets in worker source code, as the compiled worker bundle is visible to anyone with access to the Workers dashboard. The absence of a filesystem prevents path traversal attacks, and the lack of child_process eliminates command injection vulnerabilities entirely. However, edge workers process requests from untrusted clients directly, making input validation critical — use Zod or Valibot to validate every request body and URL parameter before passing data to D1 queries or KV operations. Time-of-check/time-of-use race conditions are less common in the stateless request handler model, but Durable Objects' single-threaded actor model requires careful thought about concurrent access patterns.
Testing Strategy for Edge Code
Testing edge runtime code requires tooling that accurately emulates the edge execution environment. The @cloudflare/vitest-pool-workers package runs test code inside the actual Workers runtime via workerd — the same open-source runtime that powers production Workers. This eliminates entire categories of bugs caused by Node.js polyfill differences. For unit tests of pure functions that don't use Workers-specific APIs, standard Vitest or Jest works fine. Integration tests using Miniflare (the local Workers emulator that underlies wrangler dev) can test D1 queries, KV operations, and R2 reads without deploying to production. CI pipelines should run both types: pure unit tests in a standard Vitest environment for speed, and Workers-specific integration tests using vitest-pool-workers for correctness.
Migration from Node.js to Edge
Migrating an existing Express or Fastify API to Hono on Cloudflare Workers requires systematic identification of Node.js-specific dependencies. Run your dependencies through the are-the-types-wrong tool and the Workers compatibility checker to identify packages that import fs, net, child_process, or other unavailable Node.js modules. The most common blockers are: database clients (replace pg with Hyperdrive or use D1 for SQLite workloads), Redis clients (replace ioredis with Workers KV or Upstash Redis's fetch-based client), authentication libraries (replace bcrypt with bcryptjs or Web Crypto's PBKDF2), and email libraries (replace nodemailer with a fetch-based email API like Resend or Postmark). Plan the migration as an API proxy first — run Hono on Workers as a thin proxy to your existing Node.js backend while migrating routes incrementally.
Rate Limiting at the Edge
Rate limiting is one of the most compelling use cases for edge runtimes because enforcing rate limits close to the requester eliminates the latency of a round-trip to a centralized rate limiter. Cloudflare Workers can implement request rate limiting using KV for simple per-IP counters or Durable Objects for strongly-consistent per-user rate limiting. The @cloudflare/workers-types package includes the RateLimiter binding type for Cloudflare's dedicated Rate Limiting API, which enforces limits at the network layer before your worker code even executes — the most efficient form of rate limiting available. For Vercel Edge Runtime, use @vercel/kv to store request counts per IP with a rolling window algorithm. The key implementation concern for distributed rate limiting at the edge is eventual consistency — KV stores are eventually consistent, meaning two concurrent requests to different edge locations may both see a counter below the limit and both be allowed through. For strict rate limits on sensitive operations, use Durable Objects (Cloudflare) or a centralized Redis instance (Vercel/Fly.io) rather than distributed KV.
Durable Objects and Stateful Edge Patterns
Cloudflare's Durable Objects extend the edge runtime model to support stateful workloads — each Durable Object is a single-threaded actor with its own persistent storage, running at the edge location nearest to the first requester. This unlocks patterns impossible in stateless Workers: WebSocket connection management, real-time collaboration servers, distributed rate limiters, and per-user transactional state. The @cloudflare/workers-types package includes full TypeScript types for the DurableObjectNamespace, DurableObjectStub, and DurableObjectState interfaces. Design Durable Objects carefully: because each object is single-threaded, a long-running operation blocks all other requests to that object. Use state.storage.transaction() for atomic multi-key writes, and state.waitUntil() for background work that should complete after the response is returned. Queues (Cloudflare's Queue binding) complement Durable Objects well for workloads where you need exactly-once delivery semantics across multiple edge locations rather than per-object state.
Compare edge-compatible package downloads on PkgPulse.
See also: Fastify vs Hono and Express vs Hono, Hono vs itty-router: Edge-First API Frameworks Compared.
See the live comparison
View best edge runtime npm packages on PkgPulse →