Skip to main content

Overview

The starter kit includes a complete user management system for administrators to create, edit, delete, and manage users. It features role assignment, profile management, user search, and filtering capabilities. Administrators can view and manage all users with powerful search and filtering:
app/users/controllers/users_controller.ts
import type { HttpContext } from '@adonisjs/core/http'
import User from '#users/models/user'
import UserDto from '#users/dtos/user'
import UserPolicy from '#users/policies/user_policy'

export default class UsersController {
  public async index({ bouncer, inertia, request }: HttpContext) {
    await bouncer.with(UserPolicy).authorize('viewList')

    const payload = await request.validateUsing(listUserValidator)

    const limit = payload.perPage || 10
    const page = payload.page || 1
    const querySearch = payload.q || undefined
    const roleIds = payload.roleIds || []

    const query = User.query()

    // Search by name or email
    if (querySearch) {
      query.where((subquery) => {
        subquery
          .where('full_name', 'ilike', `%${querySearch}%`)
          .orWhere('email', 'ilike', `%${querySearch}%`)
      })
    }

    // Filter by roles
    if (Array.isArray(roleIds) && roleIds.length > 0) {
      query.andWhereIn('role_id', roleIds)
    }

    const users = await query.preload('role').paginate(page, limit)
    await User.preComputeUrls(users)

    return inertia.render('users/index', {
      users: UserDto.fromPaginator(users),
      q: querySearch,
      selectedRoles: roleIds,
    })
  }
}
Authorization: Only administrators can access the user list. Authorization is enforced using Bouncer policies.

Creating Users

Administrators can create new users with assigned roles:
app/users/controllers/users_controller.ts
public async store({ bouncer, request, response }: HttpContext) {
  await bouncer.with(UserPolicy).authorize('create')

  const payload = await request.validateUsing(createUserValidator)

  const user = new User()
  user.merge({
    ...payload,
    password: payload.password ? payload.password : cuid(),
  })

  await user.save()

  return response.redirect().toRoute('users.index')
}
If no password is provided, a random CUID is generated. Users can then use the password reset flow to set their own password.

Updating Users

Users can update their own profiles, and administrators can update any user:
app/users/controllers/users_controller.ts
public async update({ bouncer, params, request, response }: HttpContext) {
  const user = await User.findOrFail(params.id)

  await bouncer.with(UserPolicy).authorize('update', user)

  const payload = await request.validateUsing(editUserValidator, { 
    meta: { userId: params.id } 
  })
  
  user.merge({
    ...payload,
    password: payload.password ? payload.password : user.password,
  })

  await user.save()

  return response.redirect().toRoute('users.index')
}

Deleting Users

Administrators can delete users (except themselves):
app/users/controllers/users_controller.ts
public async destroy({ bouncer, params, response }: HttpContext) {
  const user = await User.findOrFail(params.id)

  await bouncer.with(UserPolicy).authorize('delete', user)

  await user.delete()

  return response.redirect().toRoute('users.index')
}

Profile Management

Users can manage their own profiles including avatar uploads:
app/users/controllers/profile_controller.ts
import type { HttpContext } from '@adonisjs/core/http'
import { attachmentManager } from '@jrmc/adonis-attachment'
import User from '#users/models/user'

export default class ProfileController {
  public async show({ auth, inertia }: HttpContext) {
    await User.preComputeUrls(auth.user!)

    return inertia.render('users/profile', {
      profile: new UserDto(auth.user),
    })
  }

  public async handle({ auth, request, response }: HttpContext) {
    const { avatar, ...payload } = await request.validateUsing(updateProfileValidator)

    const user = await User.findOrFail(auth.user!.id)

    if (avatar) {
      user.avatar = await attachmentManager.createFromFile(avatar)
    }

    user.merge({ ...payload })
    await user.save()

    return response.redirect().toRoute('profile.show')
  }
}

User Roles

The starter kit includes a role-based system:
app/users/enums/role.ts
enum Roles {
  USER = 1,
  ADMIN = 2,
}

export const RoleWeights = [Roles.USER, Roles.ADMIN]

export default Roles
app/users/models/role.ts
import { DateTime } from 'luxon'
import { column, hasMany } from '@adonisjs/lucid/orm'
import User from '#users/models/user'
import BaseModel from '#common/models/base_model'

export default class Role extends BaseModel {
  @column({ isPrimary: true })
  declare id: number

  @column()
  declare name: string

  @column()
  declare description: string

  @column.dateTime({ autoCreate: true })
  declare createdAt: DateTime

  @column.dateTime({ autoCreate: true, autoUpdate: true })
  declare updatedAt: DateTime

  @hasMany(() => User)
  declare users: HasMany<typeof User>
}

User Policies

Access control is enforced through Bouncer policies:
app/users/policies/user_policy.ts
import { BasePolicy } from '@adonisjs/bouncer'
import { AuthorizerResponse } from '@adonisjs/bouncer/types'
import User from '#users/models/user'

export default class UserPolicy extends BasePolicy {
  // Only admins can view user list
  viewList(currentUser: User): AuthorizerResponse {
    return currentUser.isAdmin
  }

  // Admins can view any user, users can view themselves
  view(currentUser: User, user: User): AuthorizerResponse {
    return currentUser.isAdmin || currentUser.id === user.id
  }

  // Only admins can create users
  create(currentUser: User): AuthorizerResponse {
    return currentUser.isAdmin
  }

  // Admins can update any user, users can update themselves
  update(currentUser: User, user: User): AuthorizerResponse {
    return currentUser.isAdmin || currentUser.id === user.id
  }

  // Admins can delete users (except themselves)
  delete(currentUser: User, user: User): AuthorizerResponse {
    return currentUser.isAdmin && currentUser.id !== user.id
  }

  // Only admins can invite users
  invite(currentUser: User): AuthorizerResponse {
    return currentUser.isAdmin
  }
}

User Routes

All user management routes:
app/users/routes.ts
import { middleware } from '#start/kernel'
import router from '@adonisjs/core/services/router'

// User Management (Admin only)
router
  .resource('/users', UsersController)
  .only(['index', 'store', 'update', 'destroy'])
  .use('*', middleware.auth())
  .as('users')

// User Invitations
router.post('/users/invite', [InviteController])
  .middleware(middleware.auth())

// Profile Management
router.get('/settings/profile', [ProfileController, 'show'])
  .middleware(middleware.auth())
  .as('profile.show')

router.put('/settings/profile', [ProfileController])
  .middleware(middleware.auth())

// Password Management
router.get('/settings/password', [PasswordController, 'show'])
  .middleware(middleware.auth())
  .as('password.show')

router.put('/settings/password', [PasswordController])
  .middleware(middleware.auth())

Avatar Management

Users can upload profile avatars with automatic thumbnail generation:
app/users/models/user.ts
import { attachment, attachmentManager } from '@jrmc/adonis-attachment'
import type { Attachment } from '@jrmc/adonis-attachment/types/attachment'

export default class User extends compose(BaseModel, AuthFinder) {
  @attachment({ preComputeUrl: false, variants: ['thumbnail'] })
  declare avatar: Attachment

  @column()
  declare avatarUrl: string | null

  static async preComputeUrls(models: User | User[]) {
    if (Array.isArray(models)) {
      await Promise.all(models.map((model) => this.preComputeUrls(model)))
      return
    }

    if (!models.avatar) return

    const thumbnail = models.avatar.getVariant('thumbnail')
    if (thumbnail) {
      await attachmentManager.computeUrl(thumbnail)
    }
  }
}

User Invitations

Administrators can invite users via email with role assignment. The invited users receive an email with instructions to set their password.

Authorization

Learn about role-based access control

API Tokens

Generate tokens for API access

Search & Filtering

The user management interface supports:
  • Full-text search: Search by name or email
  • Role filtering: Filter users by assigned roles
  • Pagination: Navigate through large user lists
  • Sorting: Order users by various fields
Admin Tools: The user management system provides all the tools administrators need to efficiently manage users at scale.

Build docs developers (and LLMs) love