cross-fetch, isomorphic-fetch, and node-fetch are libraries designed to bring the fetch API to environments where it is not natively available, primarily Node.js. node-fetch is a lightweight implementation focused specifically on Node.js environments. isomorphic-fetch was an early attempt to provide a universal fetch polyfill for both Node and browsers but is now considered legacy. cross-fetch serves as the modern successor to isomorphic-fetch, offering a true polyfill that works across Node.js, browsers, and React Native without requiring environment checks.
When building JavaScript applications that run on both the server and the client, handling HTTP requests consistently is critical. cross-fetch, isomorphic-fetch, and node-fetch all aim to solve this, but they differ significantly in maintenance status, module support, and implementation strategy. Let's break down the technical realities to help you make the right choice.
The most critical factor is whether the library is still safe to use. isomorphic-fetch is officially deprecated, while cross-fetch and node-fetch remain active.
cross-fetch is actively maintained and serves as the recommended replacement for isomorphic-fetch. It receives regular updates to match the WhatWG fetch standard.
// cross-fetch: Actively maintained
import fetch from 'cross-fetch';
const res = await fetch('https://api.example.com');
isomorphic-fetch is deprecated. The repository and npm page advise users to switch to cross-fetch. Using it introduces technical debt and potential security risks due to lack of patches.
// isomorphic-fetch: Deprecated β Do not use in new projects
import 'isomorphic-fetch';
const res = await fetch('https://api.example.com');
node-fetch is actively maintained but focuses solely on Node.js. It is stable but requires version management due to breaking changes between v2 and v3.
// node-fetch: Active (Node.js only)
import fetch from 'node-fetch';
const res = await fetch('https://api.example.com');
Module compatibility is a major architectural decision, especially for node-fetch. cross-fetch and isomorphic-fetch generally handle both, but node-fetch v3 dropped CommonJS support.
cross-fetch supports both ESM and CommonJS without configuration headaches. You can import it normally in most setups.
// cross-fetch: Works in ESM and CommonJS
import fetch from 'cross-fetch';
// or
const fetch = require('cross-fetch');
isomorphic-fetch was built during the CommonJS era. While it works in legacy setups, it lacks modern ESM optimizations and tree-shaking support.
// isomorphic-fetch: Legacy CommonJS focus
import 'isomorphic-fetch';
// or
require('isomorphic-fetch');
node-fetch requires careful version selection. Version 2 supports CommonJS, but Version 3 is ESM only, which breaks many existing Node.js projects without type: module in package.json.
// node-fetch v2: CommonJS supported
const fetch = require('node-fetch');
// node-fetch v3: ESM only (requires package.json "type": "module")
import fetch from 'node-fetch';
How the library injects the fetch API matters for code cleanliness. Some packages modify the global scope, while others require explicit imports.
cross-fetch offers flexibility. You can import it as a function or use it as a polyfill to patch the global scope once.
// cross-fetch: Polyfill mode
import 'cross-fetch/polyfill';
// Now fetch() is available globally
const res = await fetch('https://api.example.com');
isomorphic-fetch works primarily as a side-effect import that patches the global scope. This can lead to hidden dependencies in large codebases.
// isomorphic-fetch: Global patching
import 'isomorphic-fetch';
// fetch() becomes available globally
const res = await fetch('https://api.example.com');
node-fetch does not patch the global scope by default. You must import and use the function explicitly, which is safer for server-side code.
// node-fetch: Explicit import only
import fetch from 'node-fetch';
// No global pollution
const res = await fetch('https://api.example.com');
All three libraries aim to implement the WhatWG fetch standard, but deviations exist in error handling and response types.
cross-fetch strives for strict adherence to the browser's native fetch API. This ensures code written for the browser works identically on the server.
// cross-fetch: Matches browser behavior
const res = await fetch('/api');
if (!res.ok) throw new Error('Request failed');
isomorphic-fetch attempted this but lacks updates for newer standard features like Request or Response constructors in some environments.
// isomorphic-fetch: Older standard implementation
const res = await fetch('/api');
// May lack newer API features
node-fetch closely mirrors the API but has slight differences in error handling. For example, network errors might reject the promise differently than in browsers.
// node-fetch: Node-specific behavior
const res = await fetch('/api');
// Network errors might need specific handling compared to browsers
| Feature | cross-fetch | isomorphic-fetch | node-fetch |
|---|---|---|---|
| Status | β Active | β Deprecated | β Active |
| Environment | π Node, Browser, React Native | π Node, Browser (Legacy) | π₯οΈ Node.js Only |
| Module System | β‘ ESM & CommonJS | π°οΈ CommonJS Focused | β οΈ v3 ESM Only, v2 CommonJS |
| Polyfill | π§ Optional Global Patch | π§ Global Patch | β Explicit Import Only |
| Recommendation | π Best for Universal Apps | π« Do Not Use | π Best for Node-Only Backends |
cross-fetch is the standard choice for universal JavaScript applications. It removes the need to write environment-specific code and is the direct successor to isomorphic-fetch. Use this for monorepos, Next.js projects, or libraries meant to run anywhere.
isomorphic-fetch should be avoided in all new development. It is legacy software that no longer receives updates. If you see this in a codebase, plan a migration to cross-fetch.
node-fetch remains the top choice for backend-only services. It is lightweight and optimized for Node.js. Just ensure you pick the right version for your module system β v2 for CommonJS, v3 for ESM.
Final Thought: For most modern frontend architects, cross-fetch provides the safest path forward for shared code. Reserve node-fetch for pure server-side utilities where browser compatibility is irrelevant.
Choose cross-fetch for new projects requiring universal support across Node.js, browsers, and React Native. It acts as a true polyfill, meaning you can import it once and use the native fetch API everywhere without changing code. It is actively maintained and recommended by the authors of isomorphic-fetch as the replacement.
Do not choose isomorphic-fetch for new projects as it is deprecated and no longer maintained. The maintainers explicitly recommend migrating to cross-fetch. Only consider this if you are maintaining a legacy codebase that cannot be refactored immediately, but plan to replace it soon.
Choose node-fetch if you are building a backend service running exclusively on Node.js and do not need browser compatibility. Be aware that version 3 is ESM only, requiring module configuration changes, while version 2 supports CommonJS. It is ideal for server-side scripts where bundle size and browser polyfills are not concerns.
Universal WHATWG Fetch API for Node, Browsers, Workers and React Native. The scenario that cross-fetch really shines is when the same JavaScript codebase needs to run on different platforms.
npm install --save cross-fetch
As a ponyfill (imports locally):
// Using ES6 modules with Babel or TypeScript
import fetch from 'cross-fetch';
// Using CommonJS modules
const fetch = require('cross-fetch');
As a polyfill (installs globally):
// Using ES6 modules
import 'cross-fetch/polyfill';
// Using CommonJS modules
require('cross-fetch/polyfill');
The CDN build is also available on unpkg:
<script src="//unpkg.com/cross-fetch/dist/cross-fetch.js"></script>
This adds the fetch function to the window object. Note that this is not UMD compatible.
With promises:
import fetch from 'cross-fetch';
// Or just: import 'cross-fetch/polyfill';
fetch('//api.github.com/users/lquixada')
.then(res => {
if (res.status >= 400) {
throw new Error("Bad response from server");
}
return res.json();
})
.then(user => {
console.log(user);
})
.catch(err => {
console.error(err);
});
With async/await:
import fetch from 'cross-fetch';
// Or just: import 'cross-fetch/polyfill';
(async () => {
try {
const res = await fetch('//api.github.com/users/lquixada');
if (res.status >= 400) {
throw new Error("Bad response from server");
}
const user = await res.json();
console.log(user);
} catch (err) {
console.error(err);
}
})();
You can find a comprehensive doc at Github's fetch page. If you want to play with cross-fetch, check our JSFiddle playground.
Tip: Run the fiddle on various browsers and with different settings (for instance: cross-domain requests, wrong urls or text requests). Don't forget to open the console in the test suite page and play around.
I did a lot of research in order to find a fetch library that could be simple, cross-platform and provide polyfill as an option. There's a plethora of libs out there but none could match those requirements.
In a word? Risk. If the spec changes in the future, it might be problematic to debug. Read more about it on sindresorhus's ponyfill page. It's up to you if you're fine with it or not.
Just like isomorphic-fetch, it is just a proxy. If you're in node, it delivers you the node-fetch library, if you're in a browser or React Native, it delivers you the github's whatwg-fetch. The same strategy applies whether you're using polyfill or ponyfill.
![]() | ![]() | ![]() | ![]() | ![]() | ![]() |
|---|---|---|---|---|---|
| The New York Times | Apollo GraphQL | Swagger | VulcanJS | graphql-request |
Heavily inspired by the works of matthew-andrews. Kudos to him!
cross-fetch is licensed under the MIT license Β© Leonardo QuixadΓ‘
| @lquixada |