Tools: AdminJS v7 in classic NestJS without tears

Tools: AdminJS v7 in classic NestJS without tears

Source: Dev.to

Simply my problem was: ## But hold up isn't there official example? ## The trick in one sentence ## Time to code ## Why this actually works well enough ## What usually goes wrong (heads up) ## Resources thats lead me to this solution 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. 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 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. Load AdminJS and friends with dynamic import() inside async function and keep everything else CommonJS like it always was. 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. create new nest project in current dir if you didn't already install all the boys. sequelize in my case and could be whatever you want just install orm's adapter from adminjs docs. create adminjs esm loader src/adminjs-loader.ts src/main.ts (almost unchanged) src/app.module.ts (the place where you actually use it) 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. Templates let you quickly answer FAQs or store snippets for re-use. Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment's permalink. Hide child comments as well For further actions, you may consider blocking this person and/or reporting abuse CODE_BLOCK: nest new . Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK: npm install sequelize adminjs @adminjs/nestjs @adminjs/sequelize @adminjs/express express-session express-formidable Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK: npm install sequelize adminjs @adminjs/nestjs @adminjs/sequelize @adminjs/express express-session express-formidable COMMAND_BLOCK: npm install sequelize adminjs @adminjs/nestjs @adminjs/sequelize @adminjs/express express-session express-formidable CODE_BLOCK: touch src/adminjs-loader.ts Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: touch src/adminjs-loader.ts CODE_BLOCK: touch src/adminjs-loader.ts CODE_BLOCK: // 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 CODE_BLOCK: // 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 }; } CODE_BLOCK: // 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 }; } CODE_BLOCK: 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 CODE_BLOCK: 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(); CODE_BLOCK: 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(); CODE_BLOCK: 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 CODE_BLOCK: 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', }, }), ], }; } } CODE_BLOCK: 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', }, }), ], }; } } - 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 - 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() - DynamicModule chapter from nestjs - Running esm packages in commonJS