DEV Community

Cover image for AdminJS v7 in classic NestJS without tears
yousef aldabbas
yousef aldabbas

Posted on

AdminJS v7 in classic NestJS without tears

I'm writing this blog because AdminJS decided to go with ESM in their latest version (v7) and make my life harder (a bit) because NestJS are still using CommonJS and theres no plan to support esm.

Simply my problem was:

Use require('adminjs') and you get a ERR_REQUIRE_ESM
Use normal import in TypeScript, wait for CJS to compile, again you get a ERR_REQUIRE_ESM

But hold up isn't there official example?

There's this repo people point to: dziraf/adminjs-v7-with-nestjs. It claims to show AdminJS v7 running smoothly with NestJS, and the AdminJS docs even link to it as an example.
Cool, right?
Except... have you actually tried cloning and running it?
It doesn't work. There's a known open issue that's been sitting there for years: Not running · Issue #1.

The trick in one sentence

Load AdminJS and friends with dynamic import() inside async function and keep everything else CommonJS like it always was.

Time to code

You can find the full working example in this repository:
👉 https://github.com/arab0v/nestjs-adminjs-starter

make sure to rollback all the changes form adminjs documentation first then lets start.

Package Installation

create new nest project in current dir if you didn't already

nest new .
Enter fullscreen mode Exit fullscreen mode

install all the boys. sequelize in my case and could be whatever you want just install orm's adapter from adminjs docs.

npm install sequelize adminjs @adminjs/nestjs @adminjs/sequelize  @adminjs/express express-session express-formidable
Enter fullscreen mode Exit fullscreen mode

create adminjs esm loader

touch src/adminjs-loader.ts
Enter fullscreen mode Exit fullscreen mode

src/adminjs-loader.ts

// This file is the only place where we touch ESM stuff
export async function loadAdminJS() {
  const [adminjs, adminjsNest, sequelizeAdapter] = await Promise.all([
    import('adminjs'),
    import('@adminjs/nestjs'),
    import('@adminjs/sequelize'),
  ]);

  const AdminJS = adminjs.default;
  const { AdminModule } = adminjsNest;

  // Tell AdminJS to use Sequelize u can use any other orm adapter
  AdminJS.registerAdapter({
    Database: sequelizeAdapter.Database,
    Resource: sequelizeAdapter.Resource,
  });

  return { AdminJS, AdminModule };
}
Enter fullscreen mode Exit fullscreen mode

src/main.ts (almost unchanged)

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  // We let AppModule do async setup (including AdminJS)
  const rootModule = await AppModule.forRoot();
  const app = await NestFactory.create(rootModule);

  await app.listen(process.env.PORT || 3000);
}

bootstrap();
Enter fullscreen mode Exit fullscreen mode

src/app.module.ts (the place where you actually use it)

import { Module } from '@nestjs/common';
import { loadAdminJS } from './adminjs-loader';

@Module({})
export class AppModule {
  static async forRoot() {
    const { AdminModule } = await loadAdminJS();

    return {
      module: AppModule,
      imports: [
        AdminModule.createAdmin({
          adminJsOptions: {
            rootPath: '/admin',
          },
        }),
      ],
    };
  }
}
Enter fullscreen mode Exit fullscreen mode

Why this actually works well enough

  • Dynamic import() is allowed in CommonJS files
  • Nothing else in your project needs to become ESM
  • No "type": "module" in package.json
  • No tsconfig "module": "nodenext" nightmare
  • No wrappers, no babel plugins, no weird loaders
  • You only pay the async price once at startup

What usually goes wrong (heads up)

  • Don’t do import AdminJS from 'adminjs' at the top of files — TypeScript will compile it → runtime crash
  • Put all AdminJS-related imports only inside loadAdminJS()

Resources thats lead me to this solution

End

This is not the most beautiful solution.
But it’s small, contained, and lets you keep running AdminJS v7 today without rewriting half your monorepo or forcing ESM on the whole team.

Good luck.

Top comments (0)