npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@udhuong/admin-core

v0.1.5

Published

Reusable admin core (client + server) — auth, RBAC, OAuth, user management. Built với NestJS + Next.js, ship trong 1 package với subpath exports.

Readme

@udhuong/admin-core

Reusable admin core cho Node.js app — auth, RBAC, OAuth, user management. Ship trong 1 package duy nhất với subpath exports: frontend (Next.js) và backend (NestJS) cùng trong 1 npm install.

npm license

Tính năng

  • 🔐 Auth — email/password + JWT (access + refresh) + 2FA/TOTP
  • 👥 User management — CRUD user, session, preferences (theme/language/notifications)
  • 🛡️ RBAC dynamic — core permissions mặc định + consumer extend extraPermissions/extraRoles qua forRoot()
  • 🔗 OAuth 2.0 — client credentials, authorization code, refresh token; social login Google/GitHub/Facebook
  • 📧 Mail events — verification, password reset, welcome (consumer tự cài mail sender)
  • 🎨 UI ready-to-use<LoginPage />, <AdminLayout />, <UsersPage />, <RolesPage />, <PermissionsPage />… với Tailwind v4 + DaisyUI
  • 🧰 NestJS moduleAdminCoreModule.forRoot() / forRootAsync() (global), auto-seed nếu import OAuthSeederModule

Install

npm install @udhuong/admin-core

Peer dependencies (optional theo phía bạn dùng)

Client (Next.js):

npm install next react react-dom
npm install --save-dev tailwindcss @tailwindcss/postcss daisyui

Server (NestJS):

npm install @nestjs/common @nestjs/core @nestjs/config @nestjs/event-emitter \
            @nestjs/jwt @nestjs/passport @nestjs/schedule @nestjs/typeorm \
            typeorm pg reflect-metadata rxjs

Tất cả @nestjs/* được khai báo là peerDependenciesMeta.optional, nên nếu bạn chỉ dùng client thì không bị warn về peer deps server (và ngược lại).


Quick start — Client (Next.js 16 App Router)

1. Wrap providers

// src/app/providers.tsx
'use client'

import { CoreProviders } from '@udhuong/admin-core'

export function Providers({ children }: { children: React.ReactNode }) {
  return (
    <CoreProviders
      config={{
        apiUrl: process.env.NEXT_PUBLIC_API_URL || 'http://localhost:4000/api',
        tokenStorageKey: 'admin_token',
        routes: {
          LOGIN: '/login',
          DASHBOARD: '/dashboard',
          UNAUTHORIZED: '/unauthorized',
          CHANGE_PASSWORD: '/change-password',
        },
      }}
    >
      {children}
    </CoreProviders>
  )
}

CoreProviders đã bao gồm QueryClientProvider, ThemeProvider, AuthProvider, AdminConfigProvider, Toaster. Không wrap lại các provider này bên ngoài.

2. Setup styles (Tailwind v4 + DaisyUI)

Tạo src/app/globals.css:

@import "tailwindcss";
@source "../../src/**/*.{ts,tsx}";
@source "../../node_modules/@udhuong/admin-core/dist/client/**/*.{js,d.ts}";
@plugin "daisyui" {
  themes: light --default, dark --prefersdark;
}

postcss.config.js:

module.exports = {
  plugins: { '@tailwindcss/postcss': {} },
}

next.config.ts:

export default {
  transpilePackages: ['@udhuong/admin-core'],
}

Import vào layout:

// src/app/layout.tsx
import './globals.css'
import { Providers } from './providers'

3. Dùng các trang có sẵn

// src/app/login/page.tsx
import { LoginPage } from '@udhuong/admin-core'
export default LoginPage

// src/app/(admin)/users/page.tsx
import { UsersPage } from '@udhuong/admin-core'
export default UsersPage

Các trang khác: RolesPage, PermissionsPage, ProfilePage, UserPreferencesPage, ChangePasswordPage, ForgotPasswordPage, ResetPasswordPage, VerifyEmailPage, UnauthorizedPage, UserDetailPage.

4. Dùng hooks

'use client'
import { useAuth } from '@udhuong/admin-core'

export default function Dashboard() {
  const { user, logout, isAuthenticated } = useAuth()
  if (!isAuthenticated) return <a href="/login">Login</a>
  return <div>Hi {user?.displayName} <button onClick={logout}>Logout</button></div>
}

Quick start — Server (NestJS 10)

1. Chuẩn bị env vars (BẮT BUỘC)

Trước khi boot, cấu hình .env:

# Root account — password >= 12 ký tự
[email protected]
ROOT_PASSWORD=your-strong-root-password

# OAuth client secrets — mỗi secret >= 16 ký tự
# Sinh: openssl rand -base64 32
OAUTH_WEB_ADMIN_SECRET=<random-32-bytes-base64>
OAUTH_WEB_PUBLIC_SECRET=<random-32-bytes-base64>
OAUTH_THIRD_PARTY_DEMO_SECRET=<random-32-bytes-base64>
OAUTH_API_SERVICE_SECRET=<random-32-bytes-base64>

JWT_ACCESS_SECRET=<random-long-string>

2. Wire AdminCoreModule + seeder

// src/app.module.ts
import 'reflect-metadata'
import { Module } from '@nestjs/common'
import { ConfigModule, ConfigService } from '@nestjs/config'
import { EventEmitterModule } from '@nestjs/event-emitter'
import { TypeOrmModule } from '@nestjs/typeorm'
import {
  AdminCoreModule,
  OAuthSeederModule,
  PermissionDef,
  RoleDef,
} from '@udhuong/admin-core/server'
import { join } from 'path'

// Extra permissions + roles riêng của app bạn
const EXTRA_PERMISSIONS: PermissionDef[] = [
  { name: 'blog.publish', resource: 'blog', action: 'publish', description: 'Publish blog posts' },
]
const EXTRA_ROLES: RoleDef[] = [
  { name: 'editor', description: 'Blog editor', permissions: ['blog.publish', 'user.read'] },
]

@Module({
  imports: [
    ConfigModule.forRoot({ isGlobal: true }),
    EventEmitterModule.forRoot(),

    TypeOrmModule.forRoot({
      type: 'postgres',
      host: 'localhost',
      port: 5432,
      username: 'postgres',
      password: 'postgres',
      database: 'myapp',
      autoLoadEntities: true,
      synchronize: true, // Chỉ dev, production dùng migrations
      // Entities từ admin-core
      entities: [
        join(__dirname, '../node_modules/@udhuong/admin-core/dist/server/**/*.entity.js'),
      ],
    }),

    AdminCoreModule.forRootAsync({
      imports: [ConfigModule],
      inject: [ConfigService],
      useFactory: (config: ConfigService) => ({
        jwt: {
          accessSecret: config.get('JWT_ACCESS_SECRET')!,
          accessExpiresIn: '1h',
        },
        rootEmail: config.get('ROOT_EMAIL')!,
        rootPassword: config.get('ROOT_PASSWORD')!,
        // BẮT BUỘC — seeder sẽ throw nếu thiếu hoặc <16 ký tự
        seeder: {
          clientSecrets: {
            webAdmin: config.get('OAUTH_WEB_ADMIN_SECRET')!,
            webPublic: config.get('OAUTH_WEB_PUBLIC_SECRET')!,
            thirdPartyDemo: config.get('OAUTH_THIRD_PARTY_DEMO_SECRET')!,
            apiService: config.get('OAUTH_API_SERVICE_SECRET')!,
          },
        },
        oauth: {
          google: {
            clientId: config.get('GOOGLE_CLIENT_ID')!,
            clientSecret: config.get('GOOGLE_CLIENT_SECRET')!,
            callbackURL: 'http://localhost:4000/api/auth/google/callback',
          },
          // github, facebook tương tự
        },
        extraPermissions: EXTRA_PERMISSIONS,
        extraRoles: EXTRA_ROLES,
      }),
    }),

    // Seed permissions + roles + OAuth scopes + clients khi boot
    OAuthSeederModule,
  ],
})
export class AppModule {}

2. Enable CORS + bootstrap

// src/main.ts
import 'reflect-metadata'
import { NestFactory } from '@nestjs/core'
import { AppModule } from './app.module'

async function bootstrap() {
  const app = await NestFactory.create(AppModule)
  app.enableCors({
    origin: process.env.CLIENT_URL || 'http://localhost:3000',
    credentials: true,
  })
  app.setGlobalPrefix('api')
  await app.listen(4000)
}
bootstrap()

3. tsconfig.json

{
  "compilerOptions": {
    "module": "node16",
    "moduleResolution": "node16",
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    "target": "ES2023"
  }
}

4. Protect endpoint với permission

import { Controller, Get, UseGuards } from '@nestjs/common'
import {
  JwtAuthGuard,
  PermissionGuard,
  RequirePermissions,
  CORE_PERMISSIONS,
} from '@udhuong/admin-core/server'

@Controller('admin/blog')
@UseGuards(JwtAuthGuard, PermissionGuard)
export class AdminBlogController {
  @Get()
  @RequirePermissions(CORE_PERMISSIONS.USER_READ.name) // hoặc truyền trực tiếp 'blog.publish'
  list() {
    return []
  }
}

Dynamic module API

AdminCoreOptions

| Key | Type | Mô tả | |-----|------|-------| | jwt.accessSecret | string | JWT signing secret — bắt buộc | | jwt.accessExpiresIn | string | mặc định '1h' | | jwt.refreshSecret | string | secret cho refresh token | | jwt.refreshExpiresIn | string | mặc định '30d' | | rootEmail | string | Email root account tạo tự động khi boot | | rootPassword | string | Mật khẩu root — bắt buộc khi root chưa tồn tại, >= 12 ký tự, đọc từ env ROOT_PASSWORD. Seeder throw nếu thiếu | | seeder.clientSecrets.webAdmin | string | Secret cho OAuth client web-adminbắt buộc, >= 16 ký tự | | seeder.clientSecrets.webPublic | string | Secret cho web-publicbắt buộc | | seeder.clientSecrets.thirdPartyDemo | string | Secret cho third-party-demobắt buộc | | seeder.clientSecrets.apiService | string | Secret cho api-servicebắt buộc | | oauth.google | { clientId, clientSecret, callbackURL } | Config OAuth Google (optional) | | oauth.github | tương tự | Config GitHub | | oauth.facebook | { appId, appSecret, callbackURL } | Config Facebook | | seeder.adminRedirectUris | string[] | Redirect URIs cho OAuth web-admin client | | seeder.webRedirectUris | string[] | cho web-public | | seeder.mobileRedirectUris | string[] | cho mobile-app | | extraPermissions | PermissionDef[] | Permissions domain của bạn, merge vào core khi seed | | extraRoles | RoleDef[] | Roles tuỳ biến, merge vào core. Dùng permissions: '*' để gán tất cả |

Core defaults

11 core permissions built-in (luôn có sau khi seed):

  • user.read, user.create, user.update, user.delete
  • role.read, role.create, role.update, role.delete
  • admin.dashboard, admin.settings, admin.analytics

3 core roles:

  • super-admin — tất cả permissions (core + extras)
  • admin — 11 core permissions
  • viewer — read-only: user.read, role.read, admin.dashboard

Gotchas — lỗi hay gặp

1. Cannot find module '@udhuong/admin-core/server'

NestJS tsconfig chưa dùng moduleResolution: node16. Đổi cả modulemoduleResolution thành node16.

2. Data type "timestamp" is not supported by "better-sqlite3"

Entities trong admin-core dùng Postgres-specific types. Dùng pg driver.

3. CORS bị block ở client

Server phải app.enableCors({ origin: CLIENT_URL, credentials: true }).

4. Login trả Invalid client

OAuth clients chưa seed. Import OAuthSeederModule vào AppModule và đảm bảo đã cấu hình seeder.clientSecrets. Login với clientCode + clientSecret do bạn tự set qua env var:

{"email":"...","password":"...","clientCode":"web-admin","clientSecret":"<OAUTH_WEB_ADMIN_SECRET>"}

4a. Seeder throw Thiếu hoặc quá ngắn (<16 ký tự) client secrets

Bạn chưa cấu hình env vars OAUTH_*_SECRET hoặc truyền chúng qua seeder.clientSecrets trong forRootAsync. Sinh secret: openssl rand -base64 32. Tuyệt đối không dùng giá trị mẫu trong docs/tests cho production.

4b. RootAccountService throw Thiếu hoặc quá ngắn rootPassword

Phải cung cấp rootPassword qua config hoặc env ROOT_PASSWORD (>= 12 ký tự) khi boot lần đầu. Sau khi root account đã tạo, có thể bỏ nếu muốn.

5. Tailwind chỉ ship vài KB, UI vỡ

@source trong CSS của package không quét được consumer source. Viết local globals.css với @source trỏ cả ../src/**/*.{ts,tsx}../node_modules/@udhuong/admin-core/dist/client/**/*.{js,d.ts}.

6. SSR hydration warning với data-theme

Thêm suppressHydrationWarning vào <html><body> trong RootLayout.

7. UserSettingsPage không còn

Đã rename thành UserPreferencesPage. SettingsPage (system-wide settings) đã bị xoá khỏi package — tự implement theo nhu cầu.

8. Next.js cần transpilePackages

Vì package ship raw CSS: transpilePackages: ['@udhuong/admin-core'] trong next.config.ts.

9. Consumer chỉ dùng client vẫn bị cảnh báo @nestjs/*

Chạy npm install với npm ≥ 7 — peer deps được mark optional qua peerDependenciesMeta, không cần cài.

10. Permissions fetch từ API trả [] cho user root

Root được tạo không tự assign role. Query DB gán role super-admin cho user root sau khi seed, hoặc thêm flow assignRootRole trong bootstrap của bạn.

11. Migration chạy tay trên DB production

Admin-core đã có migration CreateOAuthSystem trong dist/server/auth/infrastructure/database/postgresql/migrations/. TypeORM CLI:

migrations: ['node_modules/@udhuong/admin-core/dist/server/**/migrations/*.js']

Example project

Xem full setup client + server tại admin-core-example (sẽ publish riêng), hoặc tham khảo integration-test snapshot trong /examples folder của repo.


Support

Issues: https://github.com/ungdinhhuong/admin-core/issues


License

MIT © 2026 udhuong