2

I’m trying to use the Keycloak Admin Client library in my Nodejs (Typescript) application, but there's a problem about ES6/CommonJs stuff, which I never really understood (import vs require and mixing things).

Here is a snippet of my source code:

import [... whatever ...]
import KcAdminClient from '@keycloak/keycloak-admin-client';
import { Credentials } from '@keycloak/keycloak-admin-client/lib/utils/auth';

export class Controller {
  private kcAdminClient = new KcAdminClient();
  [...]

As soon as I run my application, I get the following error:

Error [ERR_REQUIRE_ESM]: require() of ES Module .../node_modules/@keycloak/keycloak-admin-client/lib/index.js from .../server/logic/auth/users.ts not supported.
Instead change the require of index.js in .../server/logic/auth/users.ts to a dynamic import() which is available in all CommonJS modules.
    at Object.<anonymous> (.../server/logic/auth/users.ts:10:49)
    at m._compile (.../node_modules/ts-node/dist/index.js:791:29)
    at require.extensions.<computed> [as .ts] (.../node_modules/ts-node/dist/index.js:793:16) {
  code: 'ERR_REQUIRE_ESM'
}

But my code does not use require(), or, at least, I haven't put any “require” string in my source file, so I assume it has something to do with how Keycloak Amin Client library is built and how I'm used to import libraries without really understanding what I am doing. In fact I'm pretty sure I faced the same problem in the past with another library, but I can't remember the solution, allegedly because I did not understand it even at the time...

Following the error message advice I tried to write a dynamic import, but I need to import a class name and all of my clumsy attempts resulted in syntax errors at compile time.

How should I import such libraries? And what is the meaning of "such" in this case?

Side note: two days ago I posted the same question on one of the official Keycloak communities, but no one even tried to comment, so I'm inclined to believe that the root cause does not lie into the library itself.

EDIT after Bench Vue answer:

I tried to add "type": "module" to my package.json as suggested, but then I got a different error:

TypeError: Unknown file extension ".ts"

which led me to another SO Question, where the accepted and most upvoted answer suggests to remove "type": "module" from package.json, or alternatively, to add "esModuleInterop": true to the compilerOptions in my tsconfig.json, which was already there, but issuing ts-node-esm server/index.ts in a terminal still yelds the same error (ERR_UNKNOWN_FILE_EXTENSION).

2 Answers 2

1

You can use dynamic import. Below is sample code

import KcAdminClient from '@keycloak/keycloak-admin-client'
import { Injectable } from '@nestjs/common'

@Injectable()
export class KeycloakService {
  kcAdminClient: KcAdminClient
  constructor() {
    setTimeout(async () => {
      this.run()
    }, 1000)
  }

  async run() {
    this.kcAdminClient = new (await this.dynamicKeycloakImport()).default()
    await this.kcAdminClient.setConfig({
      baseUrl: 'https://keycloak.dev.covergo.cloud/',
      realmName: 'distribution_dev',
    })
    const credentials = {
      grantType: 'client_credentials',
      clientId: 'client-id',
      clientSecret: 'secret',
    }

    await this.kcAdminClient.auth(credentials as any)
    const users = await this.kcAdminClient.users.find({ first: 0, max: 10 })
    console.log(users)
  }

  private dynamicKeycloakImport = async () =>
    new Function("return import('@keycloak/keycloak-admin-client')")()
}
1
  • I ended up avoidiing keycloak-admin-client altogether and calling KC endpoints directly with fetch (undici). I might consider refactoring that back to keycloak-admin-client in the future when they'll fix the issue upstream, because I'm not into this kind of workarounds (besides I'm using Typescrpt and not using NestJs), but thanks anyway. Commented Jun 14 at 6:25
0

You can use only "module" in your packages.json

"type": "module"

The reason the type field in the package.json of the @keycloak/keycloak-admin-client package indicates that it is designed to be used as an ECMAScript module (ESM).

enter image description here

The error message you encountered suggests that the Keycloak Admin Client library is an ECMAScript module (ESM), which means it should be imported using import statements, not require().

Since you didn't explicitly use require() in your code, the problem lies in how you're importing the Keycloak Admin Client library. You might be using require() implicitly, or there could be some configuration issue causing Node.js to interpret the import as a CommonJS module.

To resolve this issue, make sure you're importing the Keycloak Admin Client library using import statements, and ensure that your project's configuration (package.json) is set up to use ECMAScript modules ("type": "module").

Demo

import KcAdminClient from '@keycloak/keycloak-admin-client';

const kcAdminClient = new KcAdminClient(
    {
        baseUrl: 'http://127.0.0.1:8080',
        realmName: 'master'
    }
);

// Authorize with username / password
await kcAdminClient.auth({
  username: 'admin',
  password: 'admin',
  grantType: 'password',
  clientId: 'admin-cli',
});

// List first page of users
const users = await kcAdminClient.users.find({ first: 0, max: 10 });
console.log(JSON.stringify(users, null, 4));

In packages.json

{
  "type": "module",
  "dependencies": {
    "@keycloak/keycloak-admin-client": "^23.0.7"
  }
}

Result enter image description here


Update added TypeScript demo

Save as 'demo-user.ts'

import KcAdminClient from '@keycloak/keycloak-admin-client';

const kcAdminClient = new KcAdminClient(
    {
        baseUrl: 'http://127.0.0.1:8080',
        realmName: 'master'
    }
);

async function main() {
    // Authorize with username / password
    await kcAdminClient.auth({
        username: 'admin',
        password: 'admin',
        grantType: 'password',
        clientId: 'admin-cli',
    });

    // List first page of users
    const users = await kcAdminClient.users.find({ first: 0, max: 10 });
    console.log(JSON.stringify(users, null, 4));
}

main();

Save as tsconfig.json

{
  "compilerOptions": {
    "target": "es2015",
    "module": "ESNext",
    "strict": true,
    "esModuleInterop": true,
    "moduleResolution": "node"
  }
}

I tried es2017 and es2020 but it did not work.

In package.json

{
  "type": "module",
  "dependencies": {
    "@keycloak/keycloak-admin-client": "^23.0.7",
    "typescript": "^5.3.3"
  }
}

Transpile (or Compile)

-p or --project option with tsc, TypeScript will compile all TypeScript files in the project directory and its subdirectories according to the configuration specified in the tsconfig.json file

tsc -p ./tsconfig.json

It will generate demo-user.js file from demo-user.ts

Run it

node demo-user.js

Result

enter image description here

5
  • Thanks, but please have a look at my edited question Commented Feb 28 at 16:22
  • Did you try my demo example first? I don't know your project for other libraries. I know the '@keycloak/keycloak-admin-client' only works on ESM ("type": "module") option.
    – Bench Vue
    Commented Feb 28 at 16:40
  • Please correct me if I'm wrong, but seems to me your demo example is not using Typescript. I'm sure it works, but I need a Typescript example. Commented Feb 28 at 16:51
  • I will try to make typescript version, give me a time, I need to back go home and test it.
    – Bench Vue
    Commented Feb 28 at 16:53
  • I added the TypeScript example demo.
    – Bench Vue
    Commented Feb 28 at 22:56

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.