Skip to main content

Best npm Packages for Edge Runtimes in 2026

·PkgPulse Team
0

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.subtle instead of Node.js crypto module

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"
  }
}

Compare edge-compatible package downloads on PkgPulse.

The 2026 JavaScript Stack Cheatsheet

One PDF: the best package for every category (ORMs, bundlers, auth, testing, state management). Used by 500+ devs. Free, updated monthly.