👥 Comprehensive NestJS module for user management and profile operations
Advanced user management module providing complete user lifecycle management, profile operations, and authentication integration for HSuite applications.
Table of Contents
Quick Start
Installation
Copy npm install @hsuite/users
Basic Setup
Copy import { UsersModule } from '@hsuite/users';
@Module({
imports: [
UsersModule.forRootAsync({
useFactory: () => ({
enableProfilePictures: true,
enableEmailVerification: true,
passwordPolicy: {
minLength: 8,
requireUppercase: true,
requireNumbers: true
}
})
})
]
})
export class AppModule {}
Basic Usage
Copy @Injectable()
export class UserService {
constructor(private usersService: UsersService) {}
async createUser(userData: CreateUserDto) {
return await this.usersService.create(userData);
}
}
Architecture
Core Components
👤 User Management
UsersService
- Core user operations and business logic
UsersController
- REST API endpoints for user operations
User Entity - Database entity with full user profile support
📧 Profile Management
Profile Updates - Real-time profile modification
Avatar Management - Profile picture upload and management
Preference Settings - User preferences and configuration
🔐 Authentication Integration
Password Management - Secure password hashing and validation
Email Verification - Email confirmation workflows
Account Status - User activation, suspension, and deletion
Module Structure
Copy src/
├── entities/ # User database entities
├── dto/ # Data transfer objects
├── interfaces/ # TypeScript interfaces
├── users.service.ts # Core user service
├── users.controller.ts # HTTP endpoints
├── users.module.ts # Module configuration
└── index.ts # Public API exports
API Reference
UsersModule
Static Methods
forRootAsync(options: UsersModuleAsyncOptions): DynamicModule
Configures the users module with async dependency injection.
Copy interface UsersModuleAsyncOptions {
imports?: any[];
useFactory?: (...args: any[]) => Promise<IUsers.IOptions>;
inject?: any[];
useClass?: Type<any>;
}
Core Methods
create(userData: CreateUserDto): Promise<User>
Creates a new user with validation
Parameters : userData
- User registration information
Returns : Created user entity with assigned ID
findById(id: string): Promise<User>
Parameters : id
- User unique identifier
Returns : User entity with full profile details
findByEmail(email: string): Promise<User>
Finds user by email address
Parameters : email
- User email address
Returns : User entity or null if not found
update(id: string, updateData: UpdateUserDto): Promise<User>
Updates user profile information
Parameters : id
, updateData
- User ID and update data
Returns : Updated user entity
delete(id: string): Promise<void>
Soft deletes user account
Parameters : id
- User ID to delete
Throws : Error
if user has active dependencies
UsersController
Endpoints
POST /users
- Create new userGET /users/:id
- Get user by IDPUT /users/:id
- Update user profileDELETE /users/:id
- Delete user accountGET /users/:id/profile
- Get user profilePUT /users/:id/avatar
- Update profile picture
Guides
User Registration Guide
Learn how to implement user registration with validation and verification. Set up email confirmation, password policies, and user onboarding flows with comprehensive error handling.
Profile Management Guide
Manage user profiles, avatars, and preferences. Implement profile updates, image uploads, social links management, and privacy controls for user data.
Authentication Integration Guide
Integrate user management with authentication systems. Connect with JWT authentication, session management, and role-based access control systems.
Examples
Complete Module Configuration
Copy import { UsersModule } from '@hsuite/users';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { TypeOrmModule } from '@nestjs/typeorm';
@Module({
imports: [
UsersModule.forRootAsync({
imports: [ConfigModule],
useFactory: (configService: ConfigService) => ({
enableProfilePictures: true,
enableEmailVerification: true,
passwordPolicy: {
minLength: configService.get('PASSWORD_MIN_LENGTH', 8),
requireUppercase: true,
requireLowercase: true,
requireNumbers: true,
requireSpecialChars: false,
maxLength: 128
},
profileSettings: {
allowPublicProfiles: configService.get('ALLOW_PUBLIC_PROFILES', true),
enableBio: true,
enableSocialLinks: true,
maxBioLength: 500
},
fileUpload: {
maxAvatarSize: 5 * 1024 * 1024, // 5MB
allowedImageTypes: ['jpg', 'jpeg', 'png', 'gif'],
uploadPath: configService.get('UPLOAD_PATH', './uploads/avatars')
}
}),
inject: [ConfigService]
})
]
})
export class UserManagementModule {}
User Registration Service
Copy import { Injectable, ConflictException, BadRequestException } from '@nestjs/common';
import { UsersService } from '@hsuite/users';
import { CreateUserDto } from './dto/create-user.dto';
@Injectable()
export class UserRegistrationService {
constructor(private usersService: UsersService) {}
async registerUser(registrationData: {
email: string;
password: string;
firstName: string;
lastName: string;
organizationName?: string;
acceptedTerms: boolean;
}) {
// Validate registration data
await this.validateRegistrationData(registrationData);
try {
// Create user with pending email verification
const user = await this.usersService.create({
email: registrationData.email,
password: registrationData.password,
firstName: registrationData.firstName,
lastName: registrationData.lastName,
organizationName: registrationData.organizationName,
status: 'pending_verification',
emailVerified: false,
termsAcceptedAt: new Date(),
createdAt: new Date()
});
// Send verification email
await this.sendVerificationEmail(user);
return {
success: true,
user: {
id: user.id,
email: user.email,
firstName: user.firstName,
lastName: user.lastName,
status: user.status
},
message: 'Registration successful. Please check your email for verification.'
};
} catch (error) {
if (error.code === '23505') { // Unique constraint violation
throw new ConflictException('Email address already registered');
}
throw error;
}
}
async verifyEmail(verificationToken: string) {
const user = await this.usersService.findByVerificationToken(verificationToken);
if (!user) {
throw new BadRequestException('Invalid verification token');
}
if (user.emailVerified) {
return {
success: true,
message: 'Email already verified'
};
}
// Update user status
const updatedUser = await this.usersService.update(user.id, {
emailVerified: true,
status: 'active',
verificationToken: null,
verifiedAt: new Date()
});
return {
success: true,
user: {
id: updatedUser.id,
email: updatedUser.email,
status: updatedUser.status,
emailVerified: updatedUser.emailVerified
},
message: 'Email verified successfully'
};
}
private async validateRegistrationData(data: any) {
// Check if email is already registered
const existingUser = await this.usersService.findByEmail(data.email);
if (existingUser) {
throw new ConflictException('Email address already registered');
}
// Validate terms acceptance
if (!data.acceptedTerms) {
throw new BadRequestException('Terms and conditions must be accepted');
}
// Additional validation logic...
}
private async sendVerificationEmail(user: any) {
// Implementation for sending verification email
console.log(`Verification email sent to ${user.email}`);
}
}
Profile Management Service
Copy import { Injectable, NotFoundException, BadRequestException } from '@nestjs/common';
import { UsersService } from '@hsuite/users';
import * as path from 'path';
import * as fs from 'fs/promises';
@Injectable()
export class ProfileManagementService {
constructor(private usersService: UsersService) {}
async updateProfile(userId: string, profileData: {
firstName?: string;
lastName?: string;
bio?: string;
phoneNumber?: string;
dateOfBirth?: Date;
socialLinks?: Record<string, string>;
preferences?: Record<string, any>;
}) {
const user = await this.usersService.findById(userId);
if (!user) {
throw new NotFoundException('User not found');
}
// Validate profile data
this.validateProfileData(profileData);
const updatedUser = await this.usersService.update(userId, {
...profileData,
updatedAt: new Date()
});
return {
success: true,
user: {
id: updatedUser.id,
firstName: updatedUser.firstName,
lastName: updatedUser.lastName,
bio: updatedUser.bio,
phoneNumber: updatedUser.phoneNumber,
socialLinks: updatedUser.socialLinks,
updatedAt: updatedUser.updatedAt
},
message: 'Profile updated successfully'
};
}
async uploadAvatar(userId: string, file: Express.Multer.File) {
const user = await this.usersService.findById(userId);
if (!user) {
throw new NotFoundException('User not found');
}
// Validate file
this.validateAvatarFile(file);
try {
// Save file
const fileName = `${userId}_${Date.now()}${path.extname(file.originalname)}`;
const uploadPath = path.join('./uploads/avatars', fileName);
await fs.writeFile(uploadPath, file.buffer);
// Delete old avatar if exists
if (user.avatarUrl) {
await this.deleteOldAvatar(user.avatarUrl);
}
// Update user with new avatar URL
const updatedUser = await this.usersService.update(userId, {
avatarUrl: `/uploads/avatars/${fileName}`,
updatedAt: new Date()
});
return {
success: true,
avatarUrl: updatedUser.avatarUrl,
message: 'Avatar updated successfully'
};
} catch (error) {
throw new BadRequestException('Failed to upload avatar');
}
}
async getUserProfile(userId: string, requestingUserId?: string) {
const user = await this.usersService.findById(userId);
if (!user) {
throw new NotFoundException('User not found');
}
// Check if profile is public or user is viewing own profile
const isOwnProfile = requestingUserId === userId;
const isPublicProfile = user.profileVisibility === 'public';
if (!isOwnProfile && !isPublicProfile) {
throw new BadRequestException('Profile is private');
}
return {
id: user.id,
firstName: user.firstName,
lastName: user.lastName,
email: isOwnProfile ? user.email : null, // Only show email to owner
bio: user.bio,
avatarUrl: user.avatarUrl,
socialLinks: user.socialLinks,
joinedAt: user.createdAt,
isVerified: user.emailVerified,
profileVisibility: user.profileVisibility
};
}
async updatePreferences(userId: string, preferences: {
theme?: 'light' | 'dark';
language?: string;
notifications?: {
email: boolean;
push: boolean;
sms: boolean;
};
privacy?: {
profileVisibility: 'public' | 'private';
showEmail: boolean;
showPhoneNumber: boolean;
};
}) {
const user = await this.usersService.findById(userId);
if (!user) {
throw new NotFoundException('User not found');
}
const updatedUser = await this.usersService.update(userId, {
preferences: {
...user.preferences,
...preferences
},
updatedAt: new Date()
});
return {
success: true,
preferences: updatedUser.preferences,
message: 'Preferences updated successfully'
};
}
private validateProfileData(data: any) {
if (data.bio && data.bio.length > 500) {
throw new BadRequestException('Bio must be 500 characters or less');
}
if (data.phoneNumber && !/^\+?[\d\s\-\(\)]+$/.test(data.phoneNumber)) {
throw new BadRequestException('Invalid phone number format');
}
// Additional validation...
}
private validateAvatarFile(file: Express.Multer.File) {
const allowedTypes = ['image/jpeg', 'image/png', 'image/gif'];
if (!allowedTypes.includes(file.mimetype)) {
throw new BadRequestException('Invalid file type. Only JPEG, PNG, and GIF allowed');
}
const maxSize = 5 * 1024 * 1024; // 5MB
if (file.size > maxSize) {
throw new BadRequestException('File size too large. Maximum 5MB allowed');
}
}
private async deleteOldAvatar(avatarUrl: string) {
try {
const filePath = path.join('.', avatarUrl);
await fs.unlink(filePath);
} catch (error) {
console.error('Failed to delete old avatar:', error);
}
}
}
User Administration Service
Copy import { Injectable, NotFoundException, ForbiddenException } from '@nestjs/common';
import { UsersService } from '@hsuite/users';
@Injectable()
export class UserAdministrationService {
constructor(private usersService: UsersService) {}
async getAllUsers(adminUserId: string, filters?: {
status?: string;
role?: string;
search?: string;
page?: number;
limit?: number;
}) {
// Verify admin permissions
await this.verifyAdminPermissions(adminUserId);
const users = await this.usersService.findAll({
status: filters?.status,
role: filters?.role,
search: filters?.search,
page: filters?.page || 1,
limit: filters?.limit || 50
});
return {
users: users.data.map(user => ({
id: user.id,
email: user.email,
firstName: user.firstName,
lastName: user.lastName,
status: user.status,
role: user.role,
emailVerified: user.emailVerified,
createdAt: user.createdAt,
lastLoginAt: user.lastLoginAt
})),
pagination: {
total: users.total,
page: users.page,
limit: users.limit,
totalPages: Math.ceil(users.total / users.limit)
}
};
}
async suspendUser(adminUserId: string, userId: string, reason: string) {
await this.verifyAdminPermissions(adminUserId);
const user = await this.usersService.findById(userId);
if (!user) {
throw new NotFoundException('User not found');
}
const updatedUser = await this.usersService.update(userId, {
status: 'suspended',
suspendedAt: new Date(),
suspensionReason: reason,
suspendedBy: adminUserId
});
// Log admin action
await this.logAdminAction(adminUserId, 'suspend_user', userId, reason);
return {
success: true,
user: {
id: updatedUser.id,
status: updatedUser.status,
suspendedAt: updatedUser.suspendedAt
},
message: 'User suspended successfully'
};
}
async reactivateUser(adminUserId: string, userId: string) {
await this.verifyAdminPermissions(adminUserId);
const user = await this.usersService.findById(userId);
if (!user) {
throw new NotFoundException('User not found');
}
const updatedUser = await this.usersService.update(userId, {
status: 'active',
suspendedAt: null,
suspensionReason: null,
suspendedBy: null,
reactivatedAt: new Date(),
reactivatedBy: adminUserId
});
await this.logAdminAction(adminUserId, 'reactivate_user', userId);
return {
success: true,
user: {
id: updatedUser.id,
status: updatedUser.status,
reactivatedAt: updatedUser.reactivatedAt
},
message: 'User reactivated successfully'
};
}
async getUserStatistics() {
const stats = await this.usersService.getStatistics();
return {
totalUsers: stats.total,
activeUsers: stats.active,
suspendedUsers: stats.suspended,
pendingVerification: stats.pendingVerification,
registrationsThisMonth: stats.registrationsThisMonth,
lastLoginActivity: stats.lastLoginActivity
};
}
private async verifyAdminPermissions(userId: string) {
const user = await this.usersService.findById(userId);
if (!user || user.role !== 'admin') {
throw new ForbiddenException('Insufficient permissions');
}
}
private async logAdminAction(
adminId: string,
action: string,
targetUserId: string,
reason?: string
) {
// Implementation for logging admin actions
console.log(`Admin ${adminId} performed ${action} on user ${targetUserId}`);
}
}
Integration
Required Dependencies
Copy {
"@hsuite/users-types": "^2.0.9",
"@hsuite/auth": "^2.1.2",
"@hsuite/shared-types": "^2.0.0",
"@nestjs/typeorm": "^10.0.0",
"typeorm": "^0.3.0"
}
Environment Variables
Copy # User Management
PASSWORD_MIN_LENGTH=8
ALLOW_PUBLIC_PROFILES=true
UPLOAD_PATH=./uploads/avatars
# Email Verification
EMAIL_VERIFICATION_REQUIRED=true
VERIFICATION_TOKEN_EXPIRY=24h
# File Upload
MAX_AVATAR_SIZE=5242880
ALLOWED_IMAGE_TYPES=jpg,jpeg,png,gif
Database Schema
Copy -- Users table
CREATE TABLE users (
id VARCHAR PRIMARY KEY,
email VARCHAR UNIQUE NOT NULL,
password_hash VARCHAR NOT NULL,
first_name VARCHAR NOT NULL,
last_name VARCHAR NOT NULL,
bio TEXT,
phone_number VARCHAR,
date_of_birth DATE,
avatar_url VARCHAR,
social_links JSONB,
preferences JSONB,
status VARCHAR DEFAULT 'pending_verification',
role VARCHAR DEFAULT 'user',
email_verified BOOLEAN DEFAULT FALSE,
profile_visibility VARCHAR DEFAULT 'public',
terms_accepted_at TIMESTAMP,
verified_at TIMESTAMP,
suspended_at TIMESTAMP,
suspension_reason TEXT,
suspended_by VARCHAR,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
-- User sessions
CREATE TABLE user_sessions (
id VARCHAR PRIMARY KEY,
user_id VARCHAR REFERENCES users(id),
token_hash VARCHAR NOT NULL,
expires_at TIMESTAMP NOT NULL,
created_at TIMESTAMP DEFAULT NOW()
);
👥 User-Centric : Complete user lifecycle management from registration to administration.
🔐 Security First : Built-in password policies, email verification, and secure session management.
📱 Modern Features : Avatar uploads, social links, preferences, and comprehensive profile management.
Built with ❤️ by the HbarSuite Team
Copyright © 2024 HbarSuite. All rights reserved.