18

How can I import node-fetch package to my node project where I use typescript. I tried it in many ways... right now I am on:

const fetch = (...args) =>
    import("node-fetch").then(({ default: fetch }) => fetch(...args));

But I have 2 errors:

Cannot redeclare block-scoped variable 'fetch'.

and

A spread argument must either have a tuple type or be passed to a rest parameter.

When I change const fetch = to any other name like const fetchIt = it fixes first error but... do I really have to change that? How can I import node-fetch package in normal way where I can use fetch() method? In React I just make

import fetch from 'node-fetch';

and everything works fine... why is that so hard in node projects?

starball
  • 20,030
  • 7
  • 43
  • 238
Sowam
  • 1,674
  • 3
  • 12
  • 30
  • Have you enabled the `DOM` lib in your `tsconfig.json`? If you remove this, there shouldn't be any conflict wit the global `fetch`. – qqilihq Nov 28 '21 at 10:12
  • @qqilihq I just did `npx tsc --init` command so I have all default values there and I don't see any `DOM` there I think – Sowam Nov 28 '21 at 10:24

5 Answers5

18

If you have conflicts with a global fetch, this suggests that you have the DOM lib in your tsconfig.json enabled, e.g.:

{
  "compilerOptions": {
    "lib": ["es2021", "dom"]
  }
}

I would suggest removing this. For a NodeJS project, have a look at this answer for the suggested tsconfig.json settings depending on the NodeJS version.

After this, you will be able to import fetch as you wrote in your question and assign it to a variable named fetch, without conflicting names:

import fetch from 'node-fetch';

Alternative solution: If you need to work with 3rd party libs which are built with a browser focus and require a global fetch, have a look at the isomorphic-fetch module, which adds fetch as global function.

You will need to import it once so that fetch is available globally:

import 'isomorphic-fetch';

In this case, the compiler options of your tsconfig.json should contain the dom lib.

Update 2022-02-04

Newer NodeJS versions ship a native fetch API now, so above will no longer be necessary going forward. See here and here for more information.

qqilihq
  • 10,794
  • 7
  • 48
  • 89
5

Update:

My original post below is all true. I have been able to get it to work, however, without having to switch to ESM modules. Here is what's working in my code (based on the third code snippet in the following post: https://github.com/node-fetch/node-fetch/issues/1279#issuecomment-915063354)

// eslint-disable-next-line no-new-func
const importDynamic = new Function('modulePath', 'return import(modulePath)');

const fetch = async (...args:any[]) => {
  const module = await importDynamic('node-fetch');
  return module.default(...args);
};

Original Post:

The Cannot redeclare block-scoped variable 'fetch' error is because you're declaring a const fetch variable, and then reusing the name "fetch" as the object-destructured parameter to the then() callback later in the line.

So, changing the line to read as follows clears up that error:

const fetch = (...args) => import("node-fetch").then(module => module.default(...args));

As for the other error (A spread argument must either have a tuple type or be passed to a rest parameter.), one possible way to solve this would be to actually import the correct argument types for the fetch() call, then substitute in the above code snippet as appropriate, like this:

import { RequestInfo, RequestInit } from 'node-fetch';
const fetch = (url:RequestInfo, init?:RequestInit) => import('node-fetch').then(module => module.default(url, init));

Unfortunately, while that seems to satisfy the TypeScript compiler, I'm finding in my own code that the runtime still doesn't work (throws an ERR_REQUIRE_ESM) because my TypeScript config is currently using CommonJS modules rather than an ESM format.

See https://github.com/node-fetch/node-fetch/issues?q=ERR_REQUIRE_ESM for several open issues in the node-fetch package related to this problem.

Essentially, the node-fetch maintainers are suggesting those of us using TypeScript need to convert to ESM modules, away from CommonJS (see https://github.com/node-fetch/node-fetch/issues/1397#issuecomment-978092116 and subsequent comments).

Chris
  • 115
  • 8
4

Downgrade or install v2. npm install node-fetch@2 && npm install --save-dev @types/node-fetch@2.x

What is the point of ESM 3.x when node doesn't support it? At least for tsc. Maybe ts-node supports it. But tsc should be compatible with ts-node.

import { default as fetch, Headers } from 'node-fetch';

tsconfig

{
    "$schema": "https://json.schemastore.org/tsconfig",
    "display": "Node 12",
    "compilerOptions": {
      "lib": ["es2019"],
      "module": "commonjs",
      "target": "es2019",
      "strict": true,
      "esModuleInterop": true,
      "skipLibCheck": true,
      "forceConsistentCasingInFileNames": true,
      "moduleResolution": "node"
    },
    "include": [
        "**/*.ts"
    ]
  }

Note: Node 14, update tsconfig to es2020 from es2019 above & display Node 14

Also, Node 17+ fetch is built-in. So by the time ESM is supported in node, you probably won't need node-fetch.

TamusJRoyce
  • 817
  • 1
  • 12
  • 25
  • using tsc directly to a typescript file will ignore tsconfig.json unless specified as a command line option. This can lead to < es6 errors. Running `npx tsc` or tsc in package.json script, it will honor tsconfig.json – TamusJRoyce Dec 05 '22 at 06:25
  • The other option is to upgrade to deno. Which in some situations is a lot better than node 18.x. Both have fetch built-in. – TamusJRoyce Dec 27 '22 at 18:20
  • ESM is supported in Node for a bit. – Evert Jan 12 '23 at 18:48
  • ESM here does not work in node 14 – TamusJRoyce Jan 13 '23 at 01:48
  • 1
    Sure. Your original comment said ESM is not supported. My point is this is incorrect. ESM has worked for a while. – Evert Jan 13 '23 at 02:42
1

I suggest using cross-fetch, it is almost the same but works everywhere, it helped me because I was not able to run node-fetch in TypeScript

Oleksandr Hrin
  • 757
  • 1
  • 10
  • 12
0

This setup worked for me:

package.json

{
  "main": "index.ts",
  "scripts": {
    "start": "ts-node --esm ./src/index.ts"
  },
  "type": "module",
  "dependencies": {
    "@types/node": "^18.11.18",
    "@types/node-fetch": "^2.6.2",
    "node-fetch": "^3.3.0",
    "nodemon": "^2.0.20",
    "ts-node": "^10.9.1",
    "typescript": "^4.9.5"
  }
}

tsconfig.json

{
  "compilerOptions": {
    "allowSyntheticDefaultImports": true,
    "lib": ["ES2022", "DOM"],
    "module": "ES2022",
    "moduleResolution": "node",
    "target": "ES2022",
    "esModuleInterop": true,
  }
}

index.ts

import fetch, { Headers } from 'node-fetch';
// do something

I was using ts-node, so notice you need to apply the --esm compatibility:

ts-node --esm ./src/index.ts

Sergi Juanati
  • 1,230
  • 1
  • 9
  • 17