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 Administrator - Full system access
Administrator - User and resource management
Manager - Specific resource management
User - Access to basic features
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).
{
"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.
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.
GET /api/v1/users Authorization: Bearer <access_token>
Get by ID
Returns a user by UUID.
GET /api/v1/users/:id Authorization: Bearer <access_token>
Update
Partial update. Re-hashes password if sent and not already Argon2.
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.
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
query {
users {
id
username
firstName
lastName
email
isActive
roles
}
}
user — get by ID
query {
user(id: "00000000-0000-0000-0000-000000000001") {
id
username
firstName
lastName
email
isActive
roles
}
}
userByEmail — get by email
query {
userByEmail(email: "[email protected]") {
id
username
firstName
lastName
email
isActive
roles
}
}
Mutations
createUser — create user
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
mutation {
updateUser(
id: "00000000-0000-0000-0000-000000000001"
userData: {
isActive: false
roles: [USER]
}
) {
id
username
email
isActive
roles
}
}
removeUser — remove user
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.