User Management Module for Venturial
npm install @venturialstd/userbash
npm install @venturialstd/user
`
$3
`json
{
"@nestjs/common": "^11.0.11",
"@nestjs/core": "^11.0.5",
"@nestjs/typeorm": "^10.0.0",
"@venturialstd/core": "^1.0.16",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.1",
"typeorm": "^0.3.20"
}
`
---
๐ Module Structure
`
src/user/
โโโ src/
โ โโโ constants/
โ โ โโโ user.constant.ts # USER_STATUS enum
โ โ โโโ user.settings.constant.ts # Settings keys and config
โ โโโ decorators/
โ โ โโโ user.decorator.ts # @CurrentUserId(), @CurrentUser()
โ โ โโโ acl.decorator.ts # @RequirePermissions(), @RequireAclRoles()
โ โโโ entities/
โ โ โโโ user.entity.ts # User entity
โ โ โโโ role.entity.ts # ACL Role entity
โ โ โโโ permission.entity.ts # ACL Permission entity
โ โ โโโ role-permission.entity.ts # ACL Role-Permission junction
โ โ โโโ user-role.entity.ts # ACL User-Role junction
โ โโโ guards/
โ โ โโโ user.guard.ts # UserGuard
โ โ โโโ acl.guard.ts # PermissionGuard, AclRoleGuard
โ โโโ services/
โ โ โโโ user.service.ts # UserService
โ โ โโโ acl.service.ts # AclService
โ โโโ settings/
โ โ โโโ user.settings.ts # UserSettings interface
โ โโโ index.ts # Public API exports
โ โโโ user.module.ts # Main module
โโโ test/
โ โโโ controllers/
โ โ โโโ user-test.controller.ts # Test controller
โ โโโ migrations/ # Database migrations
โ โโโ .env.example # Environment template
โ โโโ data-source.ts # TypeORM data source
โ โโโ main.ts # Test server bootstrap
โ โโโ test-app.module.ts # Test module
โโโ package.json
โโโ tsconfig.json
โโโ README.md
`
---
๐ Quick Start
$3
`typescript
import { Module } from '@nestjs/common';
import { UserModule } from '@venturialstd/user';
@Module({
imports: [
// Basic import (uses default statuses)
UserModule.forRoot(),
// Or with custom statuses
UserModule.forRoot({
allowedStatuses: ['ACTIVE', 'INACTIVE', 'SUSPENDED', 'BANNED'],
additionalEntities: [UserEmployment, UserProfile],
}),
],
})
export class AppModule {}
`
$3
`typescript
import { Injectable } from '@nestjs/common';
import { UserService, AclService } from '@venturialstd/user';
@Injectable()
export class MyService {
constructor(
private readonly userService: UserService,
private readonly aclService: AclService,
) {}
async getUser(userId: string) {
return this.userService.getUserById(userId);
}
async assignModeratorRole(userId: string) {
const role = await this.aclService.getRoleByName('MODERATOR');
return this.aclService.assignRoleToUser(userId, role.id);
}
}
`
$3
`typescript
import { Controller, Get, UseGuards } from '@nestjs/common';
import { CurrentUserId, UserGuard, RequirePermissions, PermissionGuard } from '@venturialstd/user';
@Controller('profile')
@UseGuards(UserGuard)
export class ProfileController {
@Get()
async getProfile(@CurrentUserId() userId: string) {
return { userId };
}
@Get('admin')
@RequirePermissions('users.delete', 'content.manage')
@UseGuards(UserGuard, PermissionGuard)
async adminAction() {
return { message: 'Admin action' };
}
}
`
---
๐ ACL System
The module includes a complete database-driven Access Control List (ACL) system for managing roles and permissions. See ACL_SYSTEM.md for comprehensive documentation.
$3
- Roles: Named collections of permissions (e.g., ADMIN, MODERATOR)
- Permissions: Granular access rights in resource.action format (e.g., users.delete, posts.create)
- Multiple Roles: Users can have multiple roles with different scopes
- Temporal Access: Roles can expire automatically
- Conditional Permissions: Optional conditions on role-permission assignments
$3
`typescript
// Create roles and permissions
const adminRole = await aclService.createRole('ADMIN', 'Administrator');
const userPerm = await aclService.createPermission('users', 'delete', 'Delete users');
// Assign permission to role
await aclService.assignPermissionToRole(adminRole.id, userPerm.id);
// Assign role to user
await aclService.assignRoleToUser(userId, adminRole.id);
// Check permissions
const canDelete = await aclService.userHasPermission(userId, 'users.delete');
`
---
๐ญ Extending User Statuses
The module provides a flexible system for defining custom user statuses.
๐ญ Extending User Statuses
The module provides a flexible system for defining custom user statuses.
$3
By default, the module includes:
Statuses:
- ACTIVE - User is active and can use the system
- INACTIVE - User is inactive
- SUSPENDED - User has been suspended
- PENDING_VERIFICATION - User email needs verification
$3
Create an enum for your application-specific statuses:
`typescript
// Define custom statuses
export enum AppUserStatus {
ACTIVE = 'ACTIVE',
INACTIVE = 'INACTIVE',
SUSPENDED = 'SUSPENDED',
PENDING_VERIFICATION = 'PENDING_VERIFICATION',
ON_HOLD = 'ON_HOLD',
BANNED = 'BANNED',
DELETED = 'DELETED',
}
@Module({
imports: [
UserModule.forRoot({
allowedStatuses: Object.values(AppUserStatus),
}),
],
})
export class AppModule {}
`
$3
`typescript
@Injectable()
export class UserManagementService {
constructor(private userService: UserService) {}
// Set user status with validation
async banUser(userId: string) {
return this.userService.setStatus(userId, AppUserStatus.BANNED);
}
// Get users by status
async getBannedUsers() {
return this.userService.getUsersByStatus(AppUserStatus.BANNED);
}
// Get all allowed statuses for your application
getAllStatuses() {
return this.userService.getAllowedStatuses();
}
}
`
$3
The module automatically validates statuses:
`typescript
// โ
Valid - status is in allowedStatuses
await userService.setStatus(userId, AppUserStatus.BANNED);
// โ Invalid - throws BadRequestException
await userService.setStatus(userId, 'INVALID_STATUS');
// Error: Invalid status "INVALID_STATUS". Allowed statuses: ACTIVE, INACTIVE, SUSPENDED, ...
`
---
๐ง Extending the Module
$3
โ ๏ธ Important: The metadata and settings JSONB fields are available but NOT recommended for filterable attributes due to poor query performance.
Performance Comparison:
`sql
-- โ SLOW: JSONB query (full table scan)
SELECT * FROM "user" WHERE metadata->>'department' = 'Engineering';
-- 1M records: ~50 seconds
-- โก FAST: Indexed column query
SELECT * FROM user_employment WHERE department = 'Engineering';
-- 1M records: ~10 milliseconds
`
Result: 5,000x faster with proper indexes!
$3
Always use related entities for any data you need to query or filter.
#### Step 1: Create Your Extended Entity
`typescript
import { Entity, PrimaryGeneratedColumn, Column, OneToOne, JoinColumn, Index } from 'typeorm';
import { User } from '@venturialstd/user';
@Entity('user_employment')
@Index(['department', 'isActive']) // Composite index for common queries
export class UserEmployment {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column()
@Index() // Fast lookups by userId
userId: string;
@OneToOne(() => User)
@JoinColumn({ name: 'userId' })
user: User;
// โก Indexed fields for fast filtering
@Column({ unique: true })
employeeId: string;
@Column()
@Index() // Fast department filtering
department: string;
@Column()
jobTitle: string;
@Column({ nullable: true })
@Index() // Fast manager lookups
managerId: string;
@Column({ type: 'date' })
hireDate: Date;
@Column({ default: true })
@Index() // Fast active status queries
isActive: boolean;
@Column({ type: 'timestamptz', default: () => 'CURRENT_TIMESTAMP' })
createdAt: Date;
@Column({ type: 'timestamptz', default: () => 'CURRENT_TIMESTAMP' })
updatedAt: Date;
}
`
#### Step 2: Create Service with Efficient Queries
`typescript
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { UserService } from '@venturialstd/user';
import { UserEmployment } from './entities/user-employment.entity';
@Injectable()
export class EmployeeService {
constructor(
@InjectRepository(UserEmployment)
private employmentRepo: Repository,
private userService: UserService,
) {}
async createEmployee(data: {
firstname: string;
lastname: string;
email: string;
department: string;
employeeId: string;
jobTitle: string;
}) {
// Create base user
const user = await this.userService.createUser(
data.firstname,
data.lastname,
data.email,
);
// Create employment record
const employment = this.employmentRepo.create({
userId: user.id,
employeeId: data.employeeId,
department: data.department,
jobTitle: data.jobTitle,
hireDate: new Date(),
isActive: true,
});
await this.employmentRepo.save(employment);
return this.getEmployeeWithUser(user.id);
}
// โก FAST: Uses department index
async getEmployeesByDepartment(department: string) {
return this.employmentRepo.find({
where: { department, isActive: true },
relations: ['user'],
order: { createdAt: 'DESC' },
});
}
// โก FAST: Complex query with proper indexes
async searchEmployees(searchTerm: string, department?: string) {
const query = this.employmentRepo
.createQueryBuilder('emp')
.leftJoinAndSelect('emp.user', 'user')
.where('emp.isActive = :active', { active: true });
if (department) {
query.andWhere('emp.department = :dept', { dept: department });
}
query.andWhere(
'(user.firstname ILIKE :search OR user.lastname ILIKE :search OR emp.employeeId ILIKE :search)',
{ search: %${searchTerm}% },
);
return query.getMany();
}
async getEmployeeWithUser(userId: string) {
return this.employmentRepo.findOne({
where: { userId },
relations: ['user'],
});
}
}
`
#### Step 3: Register with UserModule
`typescript
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { UserModule } from '@venturialstd/user';
import { UserEmployment } from './entities/user-employment.entity';
import { EmployeeService } from './services/employee.service';
@Module({
imports: [
// Register your entities with UserModule
UserModule.forRoot({
additionalEntities: [UserEmployment],
}),
// Also register for your service
TypeOrmModule.forFeature([UserEmployment]),
],
providers: [EmployeeService],
exports: [EmployeeService],
})
export class EmployeeModule {}
`
#### Benefits:
- โก 100x-5000x faster queries with proper indexes
- โ
Database constraints (unique, foreign keys)
- โ
Full TypeScript type safety
- โ
Complex queries (JOINs, aggregations)
- โ
Scales to millions of records
$3
If you need some flexibility for truly dynamic, non-queryable fields:
`typescript
@Entity('user_employment')
export class UserEmployment {
// ... indexed columns for queryable fields ...
@Column()
@Index()
department: string; // โก Fast queries
@Column({ unique: true })
employeeId: string; // โก Fast lookups
// ๐ฆ FLEXIBLE: For truly dynamic, non-queryable data only
@Column({ type: 'jsonb', nullable: true })
additionalInfo: {
shirtSize?: string;
parkingSpot?: string;
badgeNumber?: string;
emergencyContact?: {
name: string;
phone: string;
};
[key: string]: any;
};
}
`
$3
See the examples/ directory for complete working examples:
- user-employee-profile.entity.ts - Entity with proper indexes
- user-employee.service.ts - Service with efficient queries
- user-employee.module.ts - Module configuration
$3
| Method | Query Performance | Flexibility | Type Safety | Best For |
|--------|------------------|-------------|-------------|----------|
| Related Entities โ
| โกโกโก Excellent | โญโญ Medium | โญโญโญ Strong | Production (99% of cases) |
| Hybrid (Entities + JSONB) | โกโกโญ Good | โญโญโญ High | โญโญโญ Strong | Some flexibility needed |
| Pure JSONB โ | โ ๏ธ Poor | โญโญโญ High | โญ Weak | Avoid for queryable data |
| EAV Pattern โ | โ ๏ธ Very Poor | โญโญโญ High | โญ Weak | Not recommended |
Decision Tree:
- Need to filter/query this data? โ Use Related Entity โ
- Just storing UI preferences? โ Can use JSONB ๐
- Truly unknown runtime schema? โ Consider Hybrid approach
---
๐ Entities
$3
The main User entity represents a user in the system:
`typescript
{
id: string; // UUID primary key
firstname: string; // User's first name
lastname: string; // User's last name
email: string; // Unique email address
phone?: string; // Optional phone number
avatar?: string; // Optional avatar URL
timezone?: string; // User's timezone
locale?: string; // User's locale (e.g., 'en-US')
status?: string; // User status (extensible, e.g., 'ACTIVE', 'SUSPENDED')
role?: string; // User role (extensible, e.g., 'ADMIN', 'USER', 'MODERATOR')
isActive: boolean; // Account active flag
isEmailVerified: boolean; // Email verification status
settings?: Record; // User settings (JSONB) - for UI preferences only
metadata?: Record; // Additional metadata (JSONB) - for non-queryable data only
createdAt: Date; // Creation timestamp
updatedAt: Date; // Last update timestamp
}
`
โ ๏ธ Important Notes:
1. role and status are extensible - Define your own values via UserModule.forRoot() configuration
2. Indexed fields - Both role and status have database indexes for fast queries
3. JSONB fields (settings, metadata) - Should only be used for data you'll never query or filter on
4. For business data - Use related entities instead of JSONB (see Extensibility section)
---
๐ API Reference
$3
#### createUser(firstname, lastname, email, phone?, avatar?, timezone?, locale?)
Creates a new user with validation.
`typescript
const user = await userService.createUser(
'John',
'Doe',
'john@example.com',
'+1234567890',
'https://avatar.url',
'America/New_York',
'en-US'
);
`
#### getUserById(userId: string)
Gets a user by their ID.
`typescript
const user = await userService.getUserById('user-uuid');
`
#### getUserByEmail(email: string)
Gets a user by their email address.
`typescript
const user = await userService.getUserByEmail('john@example.com');
`
#### updateUserProfile(userId, updates)
Updates user profile fields.
`typescript
const user = await userService.updateUserProfile('user-uuid', {
firstname: 'Jane',
timezone: 'Europe/London',
});
`
#### updateUserSettings(userId, settings)
Updates user settings.
`typescript
const user = await userService.updateUserSettings('user-uuid', {
notificationsEnabled: true,
theme: 'dark',
});
`
#### updateUserMetadata(userId, metadata)
Updates user metadata.
`typescript
const user = await userService.updateUserMetadata('user-uuid', {
role: 'ADMIN',
department: 'Engineering',
});
`
#### setUserStatus(userId, isActive)
Activates or deactivates a user.
`typescript
const user = await userService.setUserStatus('user-uuid', false);
`
#### verifyEmail(userId)
Marks a user's email as verified.
`typescript
const user = await userService.verifyEmail('user-uuid');
`
#### getActiveUsers()
Gets all active users.
`typescript
const users = await userService.getActiveUsers();
`
#### getVerifiedUsers()
Gets all users with verified emails.
`typescript
const users = await userService.getVerifiedUsers();
`
#### setRole(userId: string, role: string)
Sets a user's role with validation.
`typescript
const user = await userService.setRole('user-uuid', 'MODERATOR');
// Validates against allowedRoles from UserModule configuration
`
#### setStatus(userId: string, status: string)
Sets a user's status with validation.
`typescript
const user = await userService.setStatus('user-uuid', 'BANNED');
// Validates against allowedStatuses from UserModule configuration
`
#### getUsersByRole(role: string)
Gets all users with a specific role.
`typescript
const moderators = await userService.getUsersByRole('MODERATOR');
`
#### getUsersByStatus(status: string)
Gets all users with a specific status.
`typescript
const bannedUsers = await userService.getUsersByStatus('BANNED');
`
#### getAllowedRoles()
Gets the list of allowed roles for your application.
`typescript
const roles = userService.getAllowedRoles();
// Returns: ['ADMIN', 'USER', 'MODERATOR', ...]
`
#### getAllowedStatuses()
Gets the list of allowed statuses for your application.
`typescript
const statuses = userService.getAllowedStatuses();
// Returns: ['ACTIVE', 'INACTIVE', 'SUSPENDED', ...]
`
#### isValidRole(role: string)
Checks if a role is valid for your application.
`typescript
if (userService.isValidRole('MODERATOR')) {
// Role is valid
}
`
#### isValidStatus(status: string)
Checks if a status is valid for your application.
`typescript
if (userService.isValidStatus('BANNED')) {
// Status is valid
}
`
---
๐ Enums & Constants
$3
`typescript
enum USER_STATUS {
ACTIVE = 'ACTIVE',
INACTIVE = 'INACTIVE',
SUSPENDED = 'SUSPENDED',
PENDING_VERIFICATION = 'PENDING_VERIFICATION',
}
`
$3
`typescript
enum USER_ROLE {
ADMIN = 'ADMIN',
USER = 'USER',
}
`
$3
`typescript
const USER_SETTINGS_KEY = 'USER_SETTINGS';
`
---
๐ก๏ธ Guards & Decorators
$3
#### UserGuard
Ensures the user is authenticated and active.
`typescript
@UseGuards(UserGuard)
@Get()
async getProfile(@CurrentUserId() userId: string) {
return this.userService.getUserById(userId);
}
`
#### UserRoleGuard
Enforces role-based access control.
`typescript
@RequireRoles(USER_ROLE.ADMIN)
@UseGuards(UserGuard, UserRoleGuard)
@Delete(':id')
async deleteUser(@Param('id') id: string) {
return this.userService.setUserStatus(id, false);
}
`
$3
#### @CurrentUserId()
Extracts the current user ID from the request.
`typescript
@Get()
async getData(@CurrentUserId() userId: string) {
return this.service.getUserData(userId);
}
`
#### @CurrentUser()
Extracts the full user object from the request.
`typescript
@Get()
async getData(@CurrentUser() user: User) {
return { user };
}
`
#### @RequireRoles(...roles)
Specifies required roles for a route.
`typescript
@RequireRoles(USER_ROLE.ADMIN)
@UseGuards(UserGuard, UserRoleGuard)
@Delete(':id')
async delete(@Param('id') id: string) {
// Only admins can access
}
`
---
๐งช Test Server
The module includes a standalone test server for development and testing:
$3
`bash
cd src/user
npm run test:dev
`
The server starts on port 3003 (configurable via .env).
$3
`bash
npm run test:watch
`
$3
1. Copy .env.example to .env:
`bash
cp test/.env.example test/.env
`
2. Configure your database settings in test/.env
$3
`
POST /users - Create user
GET /users - Get all users
GET /users/active - Get active users
GET /users/verified - Get verified users
GET /users/email/:email - Get user by email
GET /users/:id - Get user by ID
PUT /users/:id/profile - Update user profile
PUT /users/:id/settings - Update user settings
PUT /users/:id/metadata - Update user metadata
PUT /users/:id/status - Update user status
PUT /users/:id/verify-email - Verify user email
DELETE /users/:id - Delete user
`
---
๐ NPM Scripts
`json
{
"build": "tsc -p tsconfig.json",
"prepublishOnly": "npm run build",
"release:patch": "npm run build && npm version patch --no-git-tag-version && npm publish",
"test:dev": "ts-node -r tsconfig-paths/register test/main.ts",
"test:watch": "nodemon --watch src --watch test --ext ts --exec npm run test:dev",
"migration:generate": "ts-node node_modules/.bin/typeorm migration:generate -d test/data-source.ts test/migrations/$npm_config_name",
"migration:run": "ts-node node_modules/.bin/typeorm migration:run -d test/data-source.ts",
"migration:revert": "ts-node node_modules/.bin/typeorm migration:revert -d test/data-source.ts"
}
`
---
๐ก Usage Examples
$3
`typescript
// Create a new user
const user = await userService.createUser(
'Jane',
'Smith',
'jane@example.com',
'+1987654321',
null,
'America/Los_Angeles',
'en-US'
);
// Verify email
await userService.verifyEmail(user.id);
`
$3
`typescript
const updatedUser = await userService.updateUserProfile(userId, {
firstname: 'Janet',
timezone: 'Europe/Paris',
locale: 'fr-FR',
});
`
$3
`typescript
// Update settings
await userService.updateUserSettings(userId, {
notificationsEnabled: true,
emailNotifications: false,
theme: 'dark',
});
// Get user with settings
const user = await userService.getUserById(userId);
console.log(user.settings);
`
$3
`typescript
// Set user role in metadata
await userService.updateUserMetadata(userId, {
role: USER_ROLE.ADMIN,
});
// Controller with role guard
@Controller('admin')
export class AdminController {
@RequireRoles(USER_ROLE.ADMIN)
@UseGuards(UserGuard, UserRoleGuard)
@Get('dashboard')
async getDashboard(@CurrentUser() user: User) {
return { admin: user };
}
}
`
$3
`typescript
// Get all active users
const activeUsers = await userService.getActiveUsers();
// Get verified users
const verifiedUsers = await userService.getVerifiedUsers();
// Find by email
const user = await userService.getUserByEmail('john@example.com');
`
---
๐ง Database Migrations
$3
`bash
npm run migration:generate --name=AddUserFields
`
$3
`bash
npm run migration:run
`
$3
`bash
npm run migration:revert
`
---
๏ฟฝ Best Practices
$3
`typescript
// โ
GOOD: Indexed columns
@Entity('user_profile')
export class UserProfile {
@Column() @Index() // Fast lookups
userId: string;
@Column() @Index() // Fast filtering
country: string;
@Column({ unique: true }) // Automatically indexed
taxId: string;
}
// โ BAD: JSONB for queryable data
user.metadata = {
country: 'US', // Will be slow to query!
taxId: '123' // No unique constraint possible!
};
`
$3
`typescript
// โ
GOOD: Non-queryable UI preferences
user.settings = {
theme: 'dark',
language: 'en',
sidebarCollapsed: true
};
// โ
GOOD: Audit/log data you never filter on
user.metadata = {
lastLoginIp: '192.168.1.1',
userAgent: 'Mozilla/5.0...',
registrationSource: 'mobile_app'
};
// โ BAD: Business data you need to query
user.metadata = {
subscriptionTier: 'premium', // Use UserSubscription entity!
department: 'Engineering', // Use UserEmployment entity!
isActive: true // Use column on User entity!
};
`
$3
`typescript
@Entity('user_subscription')
@Index(['userId', 'isActive']) // For userId + status queries
@Index(['plan', 'expiresAt']) // For plan + expiration queries
export class UserSubscription {
@Column() userId: string;
@Column() plan: string;
@Column() isActive: boolean;
@Column() expiresAt: Date;
}
// Now these queries are super fast:
await repo.find({
where: { userId: 'abc-123', isActive: true } // Uses composite index
});
await repo.find({
where: {
plan: 'premium',
expiresAt: MoreThan(new Date())
} // Uses composite index
});
`
$3
`typescript
// โ
Complex query with proper indexes
const result = await employmentRepo
.createQueryBuilder('emp')
.leftJoinAndSelect('emp.user', 'user')
.where('emp.department = :dept', { dept: 'Engineering' })
.andWhere('emp.isActive = :active', { active: true })
.andWhere('user.isEmailVerified = :verified', { verified: true })
.orderBy('emp.hireDate', 'DESC')
.limit(50)
.getMany();
`
$3
`typescript
// Use DTOs with class-validator
export class CreateEmployeeDto {
@IsString()
@MinLength(2)
firstname: string;
@IsEmail()
email: string;
@IsEnum(['Engineering', 'Sales', 'Marketing'])
department: string;
@IsPositive()
@Max(1000000)
salary: number;
}
`
$3
`typescript
// โ
GOOD: Eager load when you know you need it
const employees = await repo.find({
where: { department: 'Engineering' },
relations: ['user'] // Load user in same query
});
// โ
GOOD: Load separately for optional data
const employee = await repo.findOne({ where: { userId } });
if (needUserDetails) {
employee.user = await userService.findOne(userId);
}
// โ BAD: N+1 query problem
const employees = await repo.find({ where: { department: 'Engineering' } });
for (const emp of employees) {
emp.user = await userService.findOne(emp.userId); // Queries in loop!
}
`
$3
`typescript
async createEmployeeWithProfile(data: CreateEmployeeDto) {
return this.dataSource.transaction(async (manager) => {
// Create user
const user = await manager.save(User, {
firstname: data.firstname,
lastname: data.lastname,
email: data.email
});
// Create employment record
const employment = await manager.save(UserEmployment, {
userId: user.id,
department: data.department,
salary: data.salary
});
// If anything fails, both are rolled back
return { user, employment };
});
}
`
$3
`typescript
/**
* UserEmployment Entity
*
* Stores employee-specific information for users who are employees.
* Related to User entity via userId.
*
* Indexes:
* - userId: For fast user lookups
* - department: For filtering by department
* - (department, isActive): For active employee queries by department
*
* @example
* const emp = await employmentRepo.findOne({
* where: { userId: 'abc-123' }
* });
*/
@Entity('user_employment')
@Index(['department', 'isActive'])
export class UserEmployment {
// ...
}
`
$3
`typescript
// Consider pagination for large result sets
async getEmployeesByDepartment(
department: string,
page: number = 1,
limit: number = 50
) {
return this.employmentRepo.find({
where: { department, isActive: true },
relations: ['user'],
skip: (page - 1) * limit,
take: limit,
order: { createdAt: 'DESC' }
});
}
// Add count for pagination UI
async getEmployeeCount(department: string) {
return this.employmentRepo.count({
where: { department, isActive: true }
});
}
`
$3
`typescript
// Enable query logging in development
TypeOrmModule.forRoot({
// ...
logging: ['query', 'error', 'warn'],
maxQueryExecutionTime: 1000, // Warn if query takes > 1s
});
// Log slow queries in your service
const startTime = Date.now();
const result = await this.repo.find({ ... });
const duration = Date.now() - startTime;
if (duration > 100) {
this.logger.warn(Slow query detected: ${duration}ms);
}
`
---
๐ Migration from JSONB
If you have existing data in JSONB that needs to be queryable:
$3
`typescript
@Entity('user_employment')
@Index(['userId'])
@Index(['department'])
export class UserEmployment {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column()
userId: string;
@Column()
department: string;
@Column({ type: 'decimal', precision: 10, scale: 2 })
salary: number;
}
`
$3
`bash
npm run migration:generate --name=MigrateEmploymentData
`
$3
`typescript
export class MigrateEmploymentData1234567890 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise {
// 1. Create table with indexes
await queryRunner.query(
);
// 2. Migrate data
await queryRunner.query(
);
// 3. Clean up old data
await queryRunner.query(
);
}
public async down(queryRunner: QueryRunner): Promise {
// Migrate back if needed
await queryRunner.query(
);
await queryRunner.query(DROP TABLE IF EXISTS user_employment;);
}
}
`
$3
`bash
npm run migration:run
`
---
๐ Additional Resources
$3
- NestJS Documentation
- TypeORM Documentation
- PostgreSQL Performance Tips
$3
- @venturialstd/core - Core shared functionality
- @venturialstd/organization - Organization management module
- @venturialstd/auth` - Authentication module