Users module

User management

Administrative CRUD over REST and GraphQL, dual-ready persistence, and authorization with the Role enum.

Overview

The users module owns the administrative user lifecycle. Public registration, email verification, and password reset live in the Auth module.

Persistence

  • • TypeORM entity (PostgreSQL) — active store used by UsersService
  • • Mongoose schema (MongoDB) — dual-ready; repository registered but not injected into the service
  • • Cache invalidation with the typeorm:users:user:* / mongoose:users:user:* patterns on writes

Surfaces

  • • REST under the API prefix /users — JWT + roles required by the route
  • • GraphQL — same operations, JWT + roles required by the operation
  • • No @Public() endpoints in this module

Role enum

The roles field is optional. When provided, each value must be a member of the Role enum. Customize the enum in src/modules/auth/enums/role.enum.ts for your project.

Role enum values

These values are defined in src/modules/auth/enums/role.enum.ts. For new roles, extend the enum — do not use free-form strings.

super

Super Administrator - Full system access

admin

Administrator - User and resource management

manager

Manager - Specific resource management

user

User - Access to basic features

TypeScriptrole.enum.ts
export enum Role {
  SUPER = 'super',
  ADMIN = 'admin',
  MANAGER = 'manager',
  USER = 'user',
}

Authorization

REST and GraphQL require a valid JWT. Protect operations with @Roles(...) using members of your Role enum. RolesGuard and JwtAuthGuard resolve the request for both HTTP and GraphQL.

  • • REST: @UseGuards(JwtAuthGuard, RolesGuard) + @Roles(Role.YOUR_ROLE) on each route
  • • GraphQL: guards on the resolver + @Roles(Role.YOUR_ROLE) on queries and mutations

Data model

Main user fields (IUser interface / entity / schema):

  • • id — UUID
  • • username — unique
  • • firstName, lastName
  • • email — unique
  • • password — Argon2id hash (not exposed in GraphQL)
  • • isActive — default true (Auth registration creates inactive users)
  • • roles — Role[] (Postgres default []; Mongo default follows your project's enum)
  • • passwordResetToken / passwordResetExpires
  • • emailVerificationToken / emailVerificationExpires

DTOs

CreateUserDto

Required: firstName, lastName, username, email, password (@StrongPassword). Optional: isActive, roles (Role[]).

UpdateUserDto

All fields optional. roles validates with @IsEnum(Role, { each: true }). Email verification token fields exist on the DTO but are not GraphQL fields.

Strong password

@StrongPassword requires upper, lower, digit, special character, and blocks obvious sequences (e.g. abc, 123).

JSONCreateUserDto
{
  "firstName": "Jane",
  "lastName": "Doe",
  "username": "janedoe",
  "email": "[email protected]",
  "password": "Str0ng!Pass",  Strong password required
  "isActive": true,
  "roles": ["admin", "manager"]  roles optional — Role enum values only
}

REST API

API global prefix: use a Bearer JWT from a user that has the roles required by the routes.

Create user

Creates a user. Conflict (409) if email or username already exists.

HTTP RequestPOST
POST /api/v1/users
Content-Type: application/json
Authorization: Bearer <access_token>

{
  "firstName": "Jane",
  "lastName": "Doe",
  "username": "janedoe",
  "email": "[email protected]",
  "password": "Str0ng!Pass",
  "roles": ["admin"]
}

List users

Returns all users.

HTTP RequestGET
GET /api/v1/users
Authorization: Bearer <access_token>

Get by ID

Returns a user by UUID.

HTTP RequestGET :id
GET /api/v1/users/:id
Authorization: Bearer <access_token>

Update

Partial update. Re-hashes password if sent and not already Argon2.

HTTP RequestPATCH
PATCH /api/v1/users/:id
Content-Type: application/json
Authorization: Bearer <access_token>

{
  "isActive": false,
  "roles": ["user"]
}

Delete

Removes the user and returns a snapshot.

HTTP RequestDELETE
DELETE /api/v1/users/:id
Authorization: Bearer <access_token>

GraphQL

Operations on the app GraphQL path. Send Authorization: Bearer <token>. The schema exposes the Role enum. Returned fields: id, username, firstName, lastName, email, isActive, roles (password is not exposed).

Queries

users — list all

GraphQLquery users
query {
  users {
    id
    username
    firstName
    lastName
    email
    isActive
    roles
  }
}

user — get by ID

GraphQLquery user
query {
  user(id: "00000000-0000-0000-0000-000000000001") {
    id
    username
    firstName
    lastName
    email
    isActive
    roles
  }
}

userByEmail — get by email

GraphQLquery userByEmail
query {
  userByEmail(email: "[email protected]") {
    id
    username
    firstName
    lastName
    email
    isActive
    roles
  }
}

Mutations

createUser — create user

GraphQLmutation createUser
mutation {
  createUser(userData: {
    firstName: "Jane"
    lastName: "Doe"
    username: "janedoe"
    email: "[email protected]"
    password: "Str0ng!Pass"
    isActive: true
    roles: [ADMIN, MANAGER]
  }) {
    id
    username
    firstName
    lastName
    email
    isActive
    roles
  }
}

updateUser — update user

GraphQLmutation updateUser
mutation {
  updateUser(
    id: "00000000-0000-0000-0000-000000000001"
    userData: {
      isActive: false
      roles: [USER]
    }
  ) {
    id
    username
    email
    isActive
    roles
  }
}

removeUser — remove user

GraphQLmutation removeUser
mutation {
  removeUser(id: "00000000-0000-0000-0000-000000000001") {
    id
    username
    email
  }
}

Business rules

  • • Unique email and username (ConflictException)
  • • Argon2id with ARGON2_* env; skip re-hash if value is already Argon2
  • • Helpers: updateRoles, addRole, removeRole, replaceRole (typed with Role)
  • • Cache @EnableCache / @CacheTTL(1800) on create, findAll, update, remove; @NoCache on point finders

Auth vs Users

Auth

  • • Public registration, email verification, login, forgot/reset password
  • • Sends email via EmailModule
  • • Creates inactive users with roles: []

Users

  • • Administrative CRUD (REST + GraphQL)
  • • Assigns Role enum values
  • • Does not send email

Notes

Password in responses

REST GET and GraphQL do not expose password. The hash is loaded internally by Auth only when needed (login and password change).

Customize roles

Adjust the Role enum and @Roles(...) decorators on routes/resolvers. Do not send strings outside the enum.

Explore