Building a Robust API: A Comprehensive Guide to Notification Handling and Query Enhancement
Robust APIs

Building a Robust API: A Comprehensive Guide to Notification Handling and Query Enhancement


In modern web development, creating a robust and efficient API involves more than just defining endpoints. It requires thoughtful design and implementation of various components to ensure seamless communication, data retrieval, and user interaction. In this article, we'll explore three key components of a hypothetical web application: API response structuring, advanced query handling, and notification management.


1. Structuring API Responses

The first code snippet introduces a modular approach to structuring API responses. The ApiResponse class acts as the base class, providing a foundation for both success and error responses. This design allows for consistent response formatting across different parts of the application.

class ApiResponse {
  constructor(status, message, data, paginationResults) {
    this.status = status;
    this.message = message;
    this.data = data;
    this.paginationResults = paginationResults;
  }
}

class ApiSuccessResponse extends ApiResponse {
  constructor(message, data, paginationResults) {
    super("Success", message, data, paginationResults);
  }
}

class ApiErrorResponse extends ApiResponse {
  constructor(message, data) {
    super("Error", message, data);
  }
}

module.exports = { ApiSuccessResponse, ApiErrorResponse };        

By extending the ApiResponse class, ApiSuccessResponse and ApiErrorResponse classes streamline the creation of response objects. This modular structure enhances code organization and readability, making it easier to handle various scenarios in the API.

e.g:

Response Success API Example
Response Success API Example
Article content
Screenshot of Response Success API Example



2. Advanced Query Handling

The second code snippet introduces the ApiFeatures class, designed to enhance the flexibility and functionality of database queries. This class is particularly useful for constructing dynamic queries based on user input, enabling features like filtering, searching, sorting, field selection, and pagination.

class ApiFeatures {
  constructor(query, queryString) {
    this.query = query;
    this.queryString = queryString;
  }

  filter() {
    const queryObj = { ...this.queryString };
    const excludedFields = ["page", "sort", "limit", "fields", "keyword"];

    excludedFields.forEach((el) => delete queryObj[el]);

    let queryStr = JSON.stringify(queryObj);
    queryStr = queryStr.replace(/\b(gte|gt|lte|lt)\b/g, (match) => `$${match}`);

    this.query = this.query.find(JSON.parse(queryStr));

    return this;
  }

  search() {
    if (this.queryString.keyword) {
      const keyword = this.queryString.keyword;
      const regex = new RegExp(keyword, "i"); // Case-insensitive regex

      const keywordFilter = {
        $or: [
          { firstName: { $regex: regex } },
          { lastName: { $regex: regex } },
          { username: { $regex: regex } },
          { email: { $regex: regex } },
          { name: { $regex: regex } },
          { title: { $regex: regex } },
          { description: { $regex: regex } },
        ],
      };

      this.query = this.query.find(keywordFilter);
    }

    return this;
  }

  sort() {
    const sortBy = this.queryString.sort || "-createdAt";
    this.query = this.query.sort(sortBy);

    return this;
  }

  limitFields() {
    const fields = this.queryString.fields || "-__v";
    this.query = this.query.select(fields);

    return this;
  }

  paginate(count) {
    const page = parseInt(this.queryString.page, 10) || 1;
    const limit = parseInt(this.queryString.limit, 10) || 10;
    const skip = (page - 1) * limit;

    const pagination = {
      currentPage: page,
      limit,
      numOfPages: Math.ceil(count / limit),
      total: count,
    };

    const endIndex = page * limit;
    if (endIndex < count) {
      pagination.nextPage = page + 1;
    }

    if (skip > 0) {
      pagination.prevPage = page - 1;
    }

    this.query = this.query.skip(skip).limit(limit);
    this.paginationResults = pagination;

    return this;
  }

  paginateArray() {
    const page = parseInt(this.queryString.page, 10) || 1;
    const limit = parseInt(this.queryString.limit, 10) || 10;
    const skip = (page - 1) * limit;

    const pagination = {
      currentPage: page,
      limit,
      numOfPages: Math.ceil(this.query.length / limit),
      total: this.query.length,
    };

    const endIndex = page * limit;
    if (endIndex < this.query.length) {
      pagination.nextPage = page + 1;
    }

    if (skip > 0) {
      pagination.prevPage = page - 1;
    }

    this.query = this.query.slice(skip, skip + limit);
    this.paginationResults = pagination;

    return this;
  }
}

module.exports = ApiFeatures;        

With methods such as filter, search, sort, limitFields, and paginate, developers can easily customize queries for specific use cases. This promotes code reusability and simplifies the process of adapting queries to changing requirements.


e.g.

Article content
Paginate Array result of on specific filed.



3. Notification Handling

The third code snippet introduces the NotificationHandler class, responsible for creating and managing notifications within the application. Notifications play a crucial role in keeping users informed about relevant events, such as new user registrations, posts, categories, albums, and pages.

const Notification = require("../models/Notification");

class NotificationHandler {
  constructor(userId) {
    this.userId = userId;
  }

  async createNotification(notificationData) {
    const notification = await Notification.create({
      ...notificationData,
      userId: this.userId,
    });
    return notification;
  }

  async handleNotification(title, description, typeOfNotification) {
    const notificationData = {
      title,
      description,
      typeOfNotification,
    };

    const notification = await this.createNotification(notificationData);
    return notification;
  }

  async handleNewUserRegistration(newUser) {
    const title = "New user registered";
    const description = `${newUser.firstName} ${newUser.lastName} has registered an account`;
    const typeOfNotification = "New Registration";
    return this.handleNotification(title, description, typeOfNotification);
  }

  async handleNewPost(post) {
    const title = "New Post";
    const description = `${post.title} has been posted`;
    const typeOfNotification = "New Post";
    return this.handleNotification(title, description, typeOfNotification);
  }

  async handleNewCategory(category) {
    const title = "New Category";
    const description = `${category.name} has been added`;
    const typeOfNotification = "New Category";
    return this.handleNotification(title, description, typeOfNotification);
  }

  async handleNewAlbum(album) {
    const title = "New Album";
    const description = `${album.title} has been added`;
    const typeOfNotification = "New Album";
    return this.handleNotification(title, description, typeOfNotification);
  }

  async handleNewPage(page) {
    const title = "New Page";
    const description = `${page.name} has been added`;
    const typeOfNotification = "New Page";
    return this.handleNotification(title, description, typeOfNotification);
  }
}

module.exports = NotificationHandler;        

By encapsulating notification logic in a dedicated class, developers can easily extend or modify notification types. The NotificationHandler class provides methods like createNotification and handleNotification, along with specific handlers for different notification scenarios. This modular approach simplifies the process of integrating and customizing notifications in the application.


e.g.

Article content
Notification Example

Conclusion

In this comprehensive guide, we've explored three critical components of building a robust API: structured API responses, advanced query handling, and notification management. The modular and organized approach showcased in the provided code snippets facilitates maintainability, scalability, and adaptability in the ever-evolving landscape of web development.

By incorporating these best practices into your API development workflow, you'll be better equipped to handle diverse scenarios, provide meaningful feedback to users, and ensure a seamless and enjoyable experience within your web application.


On this wonderful journey of evolution and innovation, we realize that the path to knowledge is boundless. Each step we take represents a new beginning, and every idea we introduce illuminates a fresh pathway forward. Embracing continuous learning and approaching experiences with an open heart are the keys to our perpetual growth and ongoing success. Let us always be eager to discover more, achieve more, and share more with others. The world before us is rife with opportunities, and every day presents a new chance to grow, learn, and construct a brighter future.


MAJED ALANAZI ❤️

To view or add a comment, sign in

More articles by Majed Alanazi

  • Electronic Service Workflow System

    I embarked on a journey to develop a prototype engine that enables the conceptual management of any electronic service.…

Insights from the community

Others also viewed

Explore topics