Adding Admin feature
Better Auth, which we already use for authentication in this boilerplate, offers a complete plugin system to extend its features.
The library provides a specific plugin for managing admin users with features like role-based access control, user impersonation, and ban management. The official Better Auth documentation covers this plugin and all its options in depth.
This guide will help you quickly configure the Admin plugin in the boilerplate with NestJS and MikroORM.
File Structure
Section titled “File Structure”Since the Admin plugin is part of Better Auth and works as an integrated system, we will add all admin-related fields to existing files in the /auth folder.
Here is the file structure we will modify:
apps/api/src/├── modules/auth/│ ├── auth.entity.ts (to update)│ └── auth.config.ts (to update)Installation
Section titled “Installation”-
Add the plugin to Better Auth configuration
Section titled “Add the plugin to Better Auth configuration”In your Better Auth configuration file
apps/api/src/modules/auth/auth.config.tson the API side, add theadminplugin:import { admin } from 'better-auth/plugins'export const auth = betterAuth({// ... your existing configplugins: [admin({defaultRole: 'user',adminRoles: ['admin'],}),],})
-
Update the User entity
Section titled “Update the User entity”The Admin plugin adds several fields to the user table. Update your
Userentity inauth.entity.ts:apps/api/src/modules/auth/auth.entity.ts @Entity({ tableName: 'user' })export class User {// ... your existing properties (id, name, email, etc.)@Property()role!: string@Property({ type: 'boolean', default: false })banned!: boolean@Property({ fieldName: 'banReason', nullable: true })banReason: string | null = null@Property({ fieldName: 'banExpires', nullable: true })banExpires: Date | null = null// ... your existing timestamps and relations}Update the Session entity for impersonation
Section titled “Update the Session entity for impersonation”The Admin plugin also adds an
impersonatedByfield to track when an admin is impersonating another user:apps/api/src/modules/auth/auth.entity.ts @Entity({ tableName: 'session' })export class Session {// ... your existing properties@Property({ nullable: true })impersonatedBy!: string | null// ... your existing properties}
-
Update the database
Section titled “Update the database”Now that you have updated the entities, you need to update your database to add the new columns.
Depending on your project stage, you can either recreate your database from scratch if you’re in development without important data, or use the migration system to preserve existing data.
-
Use admin features
Section titled “Use admin features”Now that everything is configured on the API side, you can use admin-related methods from your services.
Usage examples:
import { Injectable } from '@nestjs/common'import { EntityManager } from '@mikro-orm/postgresql'import { User } from './auth.entity'@Injectable()export class UsersService {constructor(private readonly em: EntityManager) {}async ban(id: string, data: BanUserDto): Promise<void> {const user = await this.em.findOne(User, { id })if (!user) {throw new NotFoundException('User not found')}this.em.assign(user, {banned: true,banReason: data.banReason,banExpires: data.banExpires,})await this.em.flush()}async unban(id: string): Promise<void> {const user = await this.em.findOne(User, { id })if (!user) {throw new NotFoundException('User not found')}this.em.assign(user, {banned: false,banReason: null,banExpires: null,})await this.em.flush()}}
The Admin plugin is now configured and operational. You can manage user roles, ban/unban users, and impersonate users via the Better Auth client.
Going further
Section titled “Going further”NestJS Decorators & Guards
Section titled “NestJS Decorators & Guards”With @thallesp/nestjs-better-auth, prefer the built-in @Roles() decorator (admin plugin user.role) instead of forking AuthGuard.
Protect admin routes:
import { Controller, Get, Post, Body, Param } from '@nestjs/common'import { Roles, Session } from '@thallesp/nestjs-better-auth'import { LoggedInBetterAuthSession } from '../../modules/auth/auth.config'
@Controller('admin')export class AdminController { @Get('users') @Roles(['admin']) async listUsers(@Session() session: LoggedInBetterAuthSession) { // Only users with role "admin" can access this endpoint return this.userService.findAll() }
@Post('users/:id/ban') @Roles(['admin']) async banUser(@Param('id') userId: string, @Body() dto: BanUserDto) { return this.userService.ban(userId, dto) }}For organization-scoped roles, use @OrgRoles() / @RequireActiveOrg() from the same package (see the organizations addon).
You can still call Better Auth admin APIs via the injectable AuthService from @thallesp/nestjs-better-auth (authService.api / authService.instance).
Frontend admin UI examples
Section titled “Frontend admin UI examples”Here are some common patterns for building admin interfaces:
Admin check:
import { useAuth } from '@/lib/auth'
export function AdminPanel() { const { user } = useAuth()
if (user?.role !== 'admin') { return <div>Access denied</div> }
return ( <div> <h1>Admin Panel</h1> {/* Admin features */} </div> )}