User Statistics Integration

Introduction

Welcome to the final lesson of this course! So far, you have learned how to build a book catalog, track user reading progress, and add sorting and analytics to your API. In this lesson, you will learn how to provide user-specific statistics in your reading tracker API. User statistics help users see their reading progress, such as how many pages they have read or how many books they have finished. By the end of this lesson, you will know how to integrate different modules and expose a new endpoint that returns these statistics for any user.

Users And Reading Modules

Before we dive in, let’s briefly remind ourselves how the Users and Reading modules are set up and connected. In previous lessons, you created separate modules for users and for tracking reading sessions. Each module has its own service and controller. The Users module manages user data, while the Reading module keeps track of what each user is reading.

Here’s a quick look at how modules are imported and used together:

TypeScript
// src/users/users.module.ts
import { Module } from '@nestjs/common';
import { UsersService } from './users.service';
import { UsersController } from './users.controller';
import { ReadingModule } from '../reading/reading.module';

@Module({
  imports: [ReadingModule],
  controllers: [UsersController],
  providers: [UsersService],
  exports: [UsersService],
})
export class UsersModule {}

By importing ReadingModule, the Users layer can call reading.findAllForUser(userId) to obtain sessions as ground truth for statistics. This avoids duplicating persistence logic and keeps the graph of dependencies explicit and testable.

Building The User Statistics Feature

Now, let’s build the feature that calculates user statistics. The main method for this is getStats in the UsersService. Here’s the code:

TypeScript
  // src/users/users.service.ts
  getStats(userId: string) {
    this.findOne(userId);
    const sessions = this.reading.findAllForUser(userId);
    const books = this.db.getBooks() as any[];

    const totalPagesRead = sessions.reduce((sum: number, s: any) => sum + s.currentPage, 0);
    let booksCompleted = 0;
    let denom = 0;
    for (const s of sessions as any[]) {
      const b = books.find((bb: any) => bb.id === s.bookId);
      if (!b || !b.totalPages) continue;
      denom += b.totalPages;
      if (s.currentPage >= b.totalPages) booksCompleted += 1;
    }
    const avgProgressPct = denom > 0 ? totalPagesRead / denom : 0;

    return {
      userId,
      totalPagesRead,
      booksInShelf: sessions.length,
      booksCompleted,
      avgProgressPct,
    };
  }

Let’s break down what this method does:

  • this.findOne(userId): Checks if the user exists. If not, it throws an error.
  • this.reading.findAllForUser(userId): Gets all reading sessions for the user.
  • this.db.getBooks(): Gets the list of all books.
  • totalPagesRead: Adds up the current page from each reading session to get the total number of pages the user has read.
  • The loop checks each session to see if the user has finished the book (currentPage >= totalPages). It also keeps track of the total number of pages across all books in the user’s shelf.
  • avgProgressPct: Calculates the average progress as a percentage by dividing the total pages read by the total number of pages in all books the user is reading.
  • The method returns an object with all these statistics.

Example Output:

Suppose a user has read 120 pages in one book and finished another book of 200 pages. The output might look like:

JSON
{
  "userId": "someId",
  "totalPagesRead": 320,
  "booksInShelf": 2,
  "booksCompleted": 1,
  "avgProgressPct": 0.8
}

This means the user has read a total of 320 pages, has 2 books in their shelf, completed 1 book, and has an average progress of 80%.

This design keeps derivation server-side so all clients share consistent semantics. The normalized avgProgressPct (0–1) makes users comparable regardless of book lengths, and booksCompleted gives a discrete, motivating milestone. The method reads only in-memory data exposed by DatabaseService and ReadingService, keeping dependencies minimal.

Sign up

Join the 1M+ learners on CodeSignal

Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal