Skip to content

GRAPHQL and MQTT INTERGRATER-AVATAR

PREREQUISITE

GraphQL in NestJS

  • GraphQL offers a more efficient and flexible approach than traditional REST APIs for querying and manipulating data.

Introduction

This document explains the SubscriberResolver class, part of our NestJS application, focusing on its integration of GraphQL and MQTT for managing subscriber data.

Code Structure

  • SubscriberResolver: A class handling GraphQL operations related to Subscribers.
  • MQTT Integration: Using nest-mqtt for listening to MQTT topics.
  • Services: Business logic is encapsulated in the SubscriberService.

  • Key Terms:

    • Broker: The server that handles MQTT messages.
    • Topic: A string that the broker uses to filter messages for each connected client.

Process Explanation:

Connecting to the MQTT Broker:

  • The SubscriberResolver class uses the nest-mqtt module to establish a connection with the central MQTT broker.
  • The connection details, such as the broker's URL and authentication credentials, are typically configured in the application settings.

Subscribing to MQTT Topics:

  • The SubscriberResolver subscribes to specific MQTT topics (e.g., dt/#) using the @Subscribe decorator. This subscription is managed through the connection established with the central MQTT broker.
  • When a message is published on these topics, the broker routes the message to the SubscriberResolver.

Processing MQTT Messages:

  • Upon receiving a message, the SubscriberResolver invokes the mqttSubscribe method.
  • This method processes the incoming MQTT message, possibly altering subscriber data. It does so by calling a method in the SubscriberService, which contains the logic for handling these messages.

GraphQL Operations Integration:

  • The GraphQL interface allows users to perform queries and mutations concerning subscribers.
  • Queries might include fetching all subscribers or a specific subscriber, while mutations could be creating, updating, or deleting a subscriber.
  • These GraphQL operations are handled by the SubscriberResolver, which interacts with the SubscriberService to perform the necessary data manipulation or retrieval.
  • The SubscriberService could be interacting with a database to persist or retrieve subscriber data.

For the Queries and Mutations of the GraphQL Operation Intergration

  • Queries:
    • getAllSubscribers: Fetches all subscribers with optional search and filter capabilities.
    • getSpecificSubscriber: Retrieves a single subscriber by ID.
  • Mutations:
    • createSubscriber: Adds a new subscriber.
    • updateSubscriber: Updates an existing subscriber.
    • deleteSubscriber: Removes a subscriber from the system.

Central MQTT Broker Role:

  • Message Broker: Acts as the central system for message exchange between clients. In this context, it handles the MQTT messages that are relevant to subscriber data.
  • Real-time Data Handling: The broker plays a crucial role in real-time data communication, making it essential for scenarios where subscriber data changes frequently and needs immediate reflection in the application.

Security and Interceptors

  • The use of JwtAuthGuard and AuthInterceptor ensures that the API is secure and that requests are processed correctly.

Resolving Fields

  • The @ResolveField decorator is used to resolve complex fields, like linking a subscriber to its broker.

1. Decorators and Class Declaration

typescriptCopy code
@UseGuards(JwtAuthGuard)
@UseInterceptors(AuthInterceptor)
@Resolver((of) => Subscriber)
export class SubscriberResolver {
  constructor(private subscriberService: SubscriberService) {}
  ...
}
  • @UseGuards(JwtAuthGuard): This decorator applies a guard to the class, which in this case is JwtAuthGuard. It ensures that the routes handled by this resolver are protected by JWT authentication. Only authenticated users can access these routes.
  • @UseInterceptors(AuthInterceptor): This decorator applies an interceptor to all routes within the class. The AuthInterceptor could be used for tasks like logging requests, transforming response data, or handling errors.
  • @Resolver((of) => Subscriber): This decorator declares the class as a GraphQL resolver for the Subscriber entity. It enables the class to handle GraphQL queries and mutations related to Subscribers.
  • constructor(private subscriberService: SubscriberService): The constructor injects SubscriberService, allowing the resolver to utilize its methods for business logic, such as interacting with a database or external services.

  • MQTT Subscription:

    • The @Subscribe decorator marks the mqttSubscribe method to listen to MQTT messages on specified topics (dt/#).
    • When a message is published on these topics, the mqttSubscribe method processes it using subscriberService.
@Subscribe({
    topic: 'dt/#',
    // transform: payload => payload.toString(),
})
async mqttSubscribe(@Payload() payload, @Topic() topic: string) {
  try {
    return await this.subscriberService.processMqttMessage(topic, payload);
  } catch (error) {
    console.log(error, 'this my eror leon lishenga');
  }
}
  • @Subscribe: This decorator configures the method to subscribe to MQTT topics. In this case, it subscribes to any topic that matches the pattern dt/#, indicating a hierarchical topic structure.
  • mqttSubscribe: This is an asynchronous method that gets triggered when a message is published to a subscribed topic. It takes two arguments:
    • @Payload() payload: The payload of the MQTT message.
    • @Topic() topic: The MQTT topic on which the message was published.
  • Inside the method, subscriberService.processMqttMessage is called to handle the message, followed by error handling.

  • GraphQL Queries :

Fetching All Subscribers

@Query((returns) => GetAllSubscribersResponse)
async getAllSubscribers(
@Args() args: ConnectionArgs,
@Args({ name: 'search', nullable: true }) search: string,
@Args({
name: 'filters',
type: () => GetAllSubscribersInput,
nullable: true,
})
filters: GetAllSubscribersInput,
) {
return await this.subscriberService.getAllSubscribers(args, search);
}
  • @Query: Declares a GraphQL query. This query fetches all subscribers, with the return type specified as GetAllSubscribersResponse.
  • args: ConnectionArgs: These are arguments for pagination (like limit, offset).
  • search: string: An optional search string to filter the subscribers.
  • filters: GetAllSubscribersInput: Additional filters for querying subscribers.
  • The method invokes subscriberService.getAllSubscribers to fetch the data.

Fetching a Specific Subscriber

@Query((returns) => Subscriber)
async getSpecificSubscriber(@Args('id') id: string) {
return await this.subscriberService.getSubscriber(id);
}
  • This query retrieves a single subscriber by their ID.
  • @Args('id'): The argument specifies the subscriber's ID to fetch.
  • The subscriberService.getSubscriber method is used to retrieve the specific subscriber.

4. GraphQL Mutations

Creating a Subscriber

typescriptCopy code
@Mutation((returns) => Subscriber)
async createSubscriber(
  @Args('createSubscriberInput') createSubscriberInput: CreateSubscriberInput,
) {
  return this.subscriberService.createSubscriber(createSubscriberInput);
}
  • @Mutation: Declares a GraphQL mutation for creating a new subscriber.
  • createSubscriberInput: CreateSubscriberInput: The input object containing data needed to create a new subscriber.
  • Calls subscriberService.createSubscriber with the provided input to add the new subscriber.

Updating a Subscriber

typescriptCopy code
@Mutation((returns) => Subscriber)
async updateSubscriber(
  @Args('updateSubscriberInput') updateSubscriberInput: UpdateSubscriberInput,
) {
  return await this.subscriberService.updateSubscriber(updateSubscriberInput);
}
  • This mutation updates an existing subscriber's details.
  • updateSubscriberInput: UpdateSubscriberInput: Contains the updated data for the subscriber.
  • Utilizes subscriberService.updateSubscriber to perform the update.

Deleting a Subscriber

typescriptCopy code
@Mutation((returns) => String)
async deleteSubscriber(@Args('subscriberId') subscriberId: string) {

5. Field Resolver

typescriptCopy code
@ResolveField('broker', (returns) => Broker, { nullable: true })
async getItem(@Parent() sub: Subscriber) {
    return sub.broker == null ? null : await this.subscriberService.getBroker(sub.broker._id);
}
  • @ResolveField('broker', (returns) => Broker, { nullable: true }): This decorator is used for defining a field resolver. In this case, it resolves the broker field of a Subscriber entity. The returns clause indicates that the resolved field will be of type Broker, and it's marked as nullable, meaning the field can be null.
  • async getItem(@Parent() sub: Subscriber): The method getItem is used to resolve the broker field. The @Parent() decorator indicates that the argument sub is the parent object in the GraphQL query, which in this context is a Subscriber.
  • The method checks if the broker field of the Subscriber (sub.broker) is null. If it is not null, it then calls subscriberService.getBroker(sub.broker._id) to fetch the Broker entity associated with the subscriber. This is typically used when the relationship between Subscriber and Broker is not directly loaded in the initial query, and additional data fetching is needed to resolve this field.

Explanation of Field Resolver

Field resolvers in GraphQL are used for resolving fields that are not directly stored in the entity but need some form of computation or fetching from other sources. In this case, the field resolver getItem is used to fetch the Broker entity associated with a Subscriber.

When a GraphQL query requests a Subscriber and includes the broker field in the selection set, this field resolver is invoked to provide the Broker data. This approach is particularly useful in managing relationships and dependencies between different entities in a GraphQL schema, allowing for more flexible and efficient data retrieval.

5. Services and Dependency Injection in NestJS

In the provided code snippet, the concept of Services and Dependency Injection (DI) is a central aspect of NestJS's architectural design. Let's delve into how these concepts are applied in the context of your application.

Explanation of Services in NestJS

  • Services: In NestJS, services are typically used to encapsulate the business logic of an application. They are responsible for data handling, such as interacting with a database, performing computations, and implementing the application's core functionalities. Services in NestJS are usually classes annotated with @Injectable() decorator, making them a part of NestJS's dependency injection system.
  • Role in Application: In your code, the SubscriberService is a service that likely contains logic for managing Subscriber entities. This can include operations like creating, retrieving, updating, and deleting subscribers, as well as processing MQTT messages.

Example of a Service

typescriptCopy code
@Injectable()
export class SubscriberService {
  // Service methods and properties here
  ...
}

Dependency Injection (DI) in NestJS

  • Dependency Injection: DI is a design pattern in which a class requests dependencies from external sources rather than creating them. In NestJS, DI is achieved through the constructor of a class, where dependencies are provided by the NestJS IoC (Inversion of Control) container.
  • Benefits: DI decouples the creation of a dependency from the class that uses it, leading to more modular, testable, and maintainable code.

Application of DI in Provided Code

In your code, the SubscriberResolver class injects the SubscriberService through its constructor. This is a clear example of DI.

Example of Dependency Injection

typescriptCopy code
@Resolver((of) => Subscriber)
export class SubscriberResolver {
  constructor(private subscriberService: SubscriberService) {}
  ...
}
  • Here, SubscriberService is injected into SubscriberResolver. The private keyword in the constructor not only declares subscriberService as a class property but also marks it as a dependency to be injected.
  • When SubscriberResolver is instantiated, NestJS automatically provides an instance of SubscriberService to it.

Summary of DI (Dependency Injection) in the Code

  • Service Used: SubscriberService.
  • Injected Into: SubscriberResolver.
  • Purpose: To handle business logic related to subscribers and MQTT message processing, thereby separating concerns and enhancing code maintainability.

THE DIAGRAM BELOW EXPLAINS THE WHOLE PROCESS

Untitled

Explanation of the Diagram:

  1. GraphQL Interface: Represents the entry point for GraphQL queries and mutations.
  2. SubscriberResolver:
    • Receives queries and mutations from the GraphQL interface.
    • Subscribes to MQTT topics (dt/#).
    • Receives MQTT messages from the MQTT broker.
  3. SubscriberService:
    • Processes business logic for creating, updating, deleting, or retrieving subscribers.
    • Handles processing of MQTT messages received by SubscriberResolver.
  4. MQTT Broker: Manages MQTT messages and sends them to SubscriberResolver

In summary, your code demonstrates a typical use of services and dependency injection in a NestJS application, adhering to principles of clean architecture and separation of concerns. This pattern not only simplifies unit testing by allowing mock implementations of services to be easily injected but also enhances the overall maintainability and scalability of the application.