Handling Friend Requests

Introduction: Why Handling Friend Requests Matters

Welcome back! In the previous lesson, you learned how to let users send friend requests in your reading tracker app. Now, it’s time to manage those requests. Handling friend requests is a key part of any social feature. It lets users see who wants to connect with them and decide whether to accept or decline those requests.

In this lesson, you will learn how to list incoming friend requests, accept or decline them, and make sure only the right user can take action. By the end, you’ll be able to build endpoints that keep your app’s friendships secure and up to date.

Viewing Incoming Friend Requests

Let’s start by allowing users to see their incoming friend requests. Here we will implement a read endpoint that returns all pending requests where the authenticated user is the recipient. In our service file, we add a method that filters pending requests; in our controller, we expose GET /friends/requests and derive the recipient from the JWT via @CurrentUser().

// src/friends/friends.service.ts (excerpt)
import { Injectable } from '@nestjs/common';
import { DatabaseService } from '../database/database.service';

@Injectable()
export class FriendsService {
  constructor(private readonly db: DatabaseService) {}

  getIncomingRequests(userId: string) {
    return this.db
      .getFriendRequests()
      .filter((r) => r.recipientId === userId && r.status === 'pending');
  }

  // ...other methods implemented in this unit below
}
  • Filters the in-memory friendRequests store to pending requests for the given userId.
  • Uses DatabaseService.getFriendRequests() introduced previously.
  • Keeps business logic in the service; no token parsing here (controller handles identity).

In this addition, we keep the read logic simple and easily testable: a pure filter over the in-memory collection. This focuses the controller on extracting the acting user while the service owns query semantics. Because it returns only pending requests, the UI/clients can reliably present actionable items. This foundation also makes it easy to extend filtering (e.g., pagination) later if needed.

// src/friends/friends.controller.ts (excerpt)
import { Controller, Get } from '@nestjs/common';
import { FriendsService } from './friends.service';
import { CurrentUser, TokenUser } from '../common/decorators/current-user.decorator';

@Controller('friends')
export class FriendsController {
  constructor(private readonly friends: FriendsService) {}

  @Get('requests')
  getIncoming(@CurrentUser() user: TokenUser) {
    const list = this.friends.getIncomingRequests(user.userId);
    return { success: true, data: list };
  }

  // ...other endpoints implemented in this unit below
}
  • Adds GET /friends/requests to expose incoming pending requests.
  • Uses @CurrentUser() so identity is taken from the JWT (not the body).
  • Delegates filtering to the service to keep the controller thin.

By extracting the user from the token, we eliminate impersonation risks that would arise from trusting IDs in the request body. The controller remains minimal and composable, while the service consolidates the query logic. This separation also keeps unit tests straightforward: controller tests focus on wiring; service tests focus on filtering.

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