Skip to main content

User Repository

The UserRepository class provides the data-access layer for authentication-level user records. It wraps UserDbService with validation (via Zod schemas), uniqueness checks, and consistent error handling.

Source file: template/lib/repositories/user.repository.ts


Architecture Overview

API Route / Server Action
|
UserRepository <-- validation, uniqueness checks, error wrapping
|
UserDbService <-- database CRUD via Drizzle ORM
|
PostgreSQL / SQLite <-- users table

Unlike the Git-backed repositories (items, tags, categories), the User Repository operates directly against the relational database through UserDbService.


Class Definition

export class UserRepository {
private userDbService: UserDbService;

constructor() {
this.userDbService = new UserDbService();
}
}

Dependencies

ImportPurpose
UserDbServiceDatabase service for user CRUD operations
AuthUserDataType representing an authenticated user record
CreateUserRequest / UpdateUserRequestRequest DTOs for create and update
UserListOptionsFiltering and pagination options
AuthUserListResponsePaginated response type
userValidationSchema / updateUserValidationSchemaZod validation schemas

Query Methods

findAll(options?): Promise<AuthUserListResponse>

Returns a paginated list of users with optional filtering.

async findAll(options: UserListOptions = {}): Promise<AuthUserListResponse>

Return type:

interface AuthUserListResponse {
users: AuthUserData[];
total: number;
page: number;
limit: number;
totalPages: number;
}

Delegates to userDbService.findUsers(options) and maps the result into the standard paginated response shape. Wraps all database errors with a generic "Failed to retrieve users" message.


findById(id): Promise<AuthUserData | null>

Retrieves a single user by their unique identifier.

async findById(id: string): Promise<AuthUserData | null>

Returns null when no user matches.


getAllUsers(): Promise<AuthUserData[]>

Returns every user record without pagination. Intended for use in admin dropdowns, assignment lists, and similar UI elements where the full user set is needed.

async getAllUsers(): Promise<AuthUserData[]>

getStats(): Promise<UserStats>

Returns aggregate user statistics.

async getStats(): Promise<{
total: number;
active: number;
inactive: number;
}>

Delegates to userDbService.getUserStats().


usernameExists(username, excludeId?): Promise<boolean>

Checks whether a given username is already taken. Optionally excludes a specific user ID (useful during updates).

async usernameExists(username: string, excludeId?: string): Promise<boolean>

Delegates to userDbService.clientProfileUsernameExists.


emailExists(email, excludeId?): Promise<boolean>

Checks whether an email address is already in use. Performs a case-insensitive comparison by loading all users and filtering in memory.

async emailExists(email: string, excludeId?: string): Promise<boolean>

Mutation Methods

create(data): Promise<AuthUserData>

Creates a new user after validation and uniqueness enforcement.

async create(data: CreateUserRequest): Promise<AuthUserData>

Processing steps:

  1. Validates email and password fields using userValidationSchema.pick(...) (Zod)
  2. Checks email uniqueness via userDbService.emailExists
  3. Creates the user record through userDbService.createUser

Throws an Error("Email already in use") if the email is taken.


update(id, data): Promise<AuthUserData>

Updates an existing user after validation and existence check.

async update(id: string, data: UpdateUserRequest): Promise<AuthUserData>

Processing steps:

  1. Validates input with updateUserValidationSchema.parse(data) (Zod)
  2. Verifies the user exists via findById
  3. Applies the update through userDbService.updateUser

Throws an Error("User not found") if the target user does not exist.


delete(id): Promise<void>

Permanently deletes a user record.

async delete(id: string): Promise<void>

Processing steps:

  1. Verifies the user exists via findById
  2. Deletes through userDbService.deleteUser

Throws an Error("User not found") if the target does not exist.

Note: Role-based deletion checks are handled at the profile level since AuthUserData contains only authentication information.


Error Handling Pattern

All public methods follow a consistent error-handling strategy:

try {
// ... operation ...
} catch (error) {
if (error instanceof Error) {
throw error; // Re-throw domain errors (validation, not-found)
}
console.error('Error [operation]:', error);
throw new Error('Failed to [operation]'); // Generic fallback
}

This ensures domain-specific errors (email taken, user not found) propagate cleanly to API routes while unexpected errors are logged and replaced with safe messages.


Validation Schemas

The repository uses two Zod schemas from @/lib/types/user:

  • userValidationSchema -- full user creation schema; the repository picks only email and password
  • updateUserValidationSchema -- partial update schema that validates whichever fields are provided

Usage Example

import { UserRepository } from '@/lib/repositories/user.repository';

const userRepo = new UserRepository();

// List users with pagination
const result = await userRepo.findAll({ page: 1, limit: 25 });

// Create a new user
const newUser = await userRepo.create({
email: 'new@example.com',
password: 'securePassword123',
});

// Check availability
const taken = await userRepo.emailExists('test@example.com');

// Get statistics
const stats = await userRepo.getStats();
// => { total: 150, active: 142, inactive: 8 }

FileRelationship
lib/services/user-db.service.tsUnderlying database service
lib/types/user.tsType definitions and Zod schemas
lib/db/drizzle.tsDatabase connection and Drizzle instance
lib/repositories/role.repository.tsRole management (related to user permissions)