DEV Community

Nadim Chowdhury
Nadim Chowdhury

Posted on • Updated on

Detail implementation of Communication Tools

Below is an implementation for communication tools including a messaging system, email notifications, and announcements/notices using Next.js, NestJS, and GraphQL.

Backend (NestJS)

1. Entities

Message Entity:

// message.entity.ts
import { Entity, Column, PrimaryGeneratedColumn, ManyToOne, CreateDateColumn } from 'typeorm';
import { User } from './user.entity';

@Entity()
export class Message {
  @PrimaryGeneratedColumn()
  id: number;

  @ManyToOne(() => User, (user) => user.sentMessages)
  sender: User;

  @ManyToOne(() => User, (user) => user.receivedMessages)
  receiver: User;

  @Column()
  content: string;

  @CreateDateColumn()
  timestamp: Date;
}
Enter fullscreen mode Exit fullscreen mode

Announcement Entity:

// announcement.entity.ts
import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn } from 'typeorm';

@Entity()
export class Announcement {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  title: string;

  @Column()
  content: string;

  @CreateDateColumn()
  createdAt: Date;
}
Enter fullscreen mode Exit fullscreen mode

2. Services

Message Service:

// message.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Message } from './message.entity';

@Injectable()
export class MessageService {
  constructor(
    @InjectRepository(Message)
    private messageRepository: Repository<Message>,
  ) {}

  async findAll(): Promise<Message[]> {
    return this.messageRepository.find({ relations: ['sender', 'receiver'] });
  }

  async create(senderId: number, receiverId: number, content: string): Promise<Message> {
    const newMessage = this.messageRepository.create({
      sender: { id: senderId },
      receiver: { id: receiverId },
      content,
    });
    return this.messageRepository.save(newMessage);
  }
}
Enter fullscreen mode Exit fullscreen mode

Announcement Service:

// announcement.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Announcement } from './announcement.entity';

@Injectable()
export class AnnouncementService {
  constructor(
    @InjectRepository(Announcement)
    private announcementRepository: Repository<Announcement>,
  ) {}

  async findAll(): Promise<Announcement[]> {
    return this.announcementRepository.find();
  }

  async create(title: string, content: string): Promise<Announcement> {
    const newAnnouncement = this.announcementRepository.create({ title, content });
    return this.announcementRepository.save(newAnnouncement);
  }
}
Enter fullscreen mode Exit fullscreen mode

3. Resolvers

Message Resolver:

// message.resolver.ts
import { Resolver, Query, Mutation, Args } from '@nestjs/graphql';
import { MessageService } from './message.service';
import { Message } from './message.entity';

@Resolver(() => Message)
export class MessageResolver {
  constructor(private messageService: MessageService) {}

  @Query(() => [Message])
  async messages() {
    return this.messageService.findAll();
  }

  @Mutation(() => Message)
  async sendMessage(
    @Args('senderId') senderId: number,
    @Args('receiverId') receiverId: number,
    @Args('content') content: string,
  ) {
    return this.messageService.create(senderId, receiverId, content);
  }
}
Enter fullscreen mode Exit fullscreen mode

Announcement Resolver:

// announcement.resolver.ts
import { Resolver, Query, Mutation, Args } from '@nestjs/graphql';
import { AnnouncementService } from './announcement.service';
import { Announcement } from './announcement.entity';

@Resolver(() => Announcement)
export class AnnouncementResolver {
  constructor(private announcementService: AnnouncementService) {}

  @Query(() => [Announcement])
  async announcements() {
    return this.announcementService.findAll();
  }

  @Mutation(() => Announcement)
  async createAnnouncement(
    @Args('title') title: string,
    @Args('content') content: string,
  ) {
    return this.announcementService.create(title, content);
  }
}
Enter fullscreen mode Exit fullscreen mode

Frontend (Next.js)

1. Apollo Client Setup

// apollo-client.js
import { ApolloClient, InMemoryCache } from '@apollo/client';

const client = new ApolloClient({
  uri: 'http://localhost:3000/graphql',
  cache: new InMemoryCache(),
});

export default client;
Enter fullscreen mode Exit fullscreen mode

2. Messaging System Page

// pages/messages.js
import { useState } from 'react';
import { useQuery, useMutation, gql } from '@apollo/client';

const GET_MESSAGES = gql`
  query GetMessages {
    messages {
      id
      content
      timestamp
      sender {
        username
      }
      receiver {
        username
      }
    }
  }
`;

const SEND_MESSAGE = gql`
  mutation SendMessage($senderId: Int!, $receiverId: Int!, $content: String!) {
    sendMessage(senderId: $senderId, receiverId: $receiverId, content: $content) {
      id
      content
      timestamp
    }
  }
`;

export default function Messages() {
  const { loading, error, data } = useQuery(GET_MESSAGES);
  const [sendMessage] = useMutation(SEND_MESSAGE);
  const [senderId, setSenderId] = useState('');
  const [receiverId, setReceiverId] = useState('');
  const [content, setContent] = useState('');

  const handleSubmit = async (e) => {
    e.preventDefault();
    await sendMessage({ variables: { senderId: parseInt(senderId), receiverId: parseInt(receiverId), content } });
    setSenderId('');
    setReceiverId('');
    setContent('');
  };

  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error: {error.message}</p>;

  return (
    <div>
      <h1>Messages</h1>
      <form onSubmit={handleSubmit}>
        <input
          type="number"
          placeholder="Sender ID"
          value={senderId}
          onChange={(e) => setSenderId(e.target.value)}
        />
        <input
          type="number"
          placeholder="Receiver ID"
          value={receiverId}
          onChange={(e) => setReceiverId(e.target.value)}
        />
        <textarea
          placeholder="Message Content"
          value={content}
          onChange={(e) => setContent(e.target.value)}
        ></textarea>
        <button type="submit">Send Message</button>
      </form>
      <ul>
        {data.messages.map((msg) => (
          <li key={msg.id}>
            <strong>{msg.sender.username}</strong> to <strong>{msg.receiver.username}</strong>: {msg.content} <em>at {msg.timestamp}</em>
          </li>
        ))}
      </ul>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

3. Announcements Page

// pages/announcements.js
import { useState } from 'react';
import { useQuery, useMutation, gql } from '@apollo/client';

const GET_ANNOUNCEMENTS = gql`
  query GetAnnouncements {
    announcements {
      id
      title
      content
      createdAt
    }
  }
`;

const CREATE_ANNOUNCEMENT = gql`
  mutation CreateAnnouncement($title: String!, $content: String!) {
    createAnnouncement(title: $title, content: $content) {
      id
      title
      content
      createdAt
    }
  }
`;

export default function Announcements() {
  const { loading, error, data } = useQuery(GET_ANNOUNCEMENTS);
  const [createAnnouncement] = useMutation(CREATE_ANNOUNCEMENT);
  const [title, setTitle] = useState('');
  const [content, setContent] = useState('');

  const handleSubmit = async (e) => {
    e.preventDefault();
    await createAnnouncement({ variables: { title, content } });
    setTitle('');
    setContent('');
  };

  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error: {error.message}</p>;

  return (
    <div>
      <h1>Announcements</h1>
      <form onSubmit={handleSubmit}>
        <input
          type="text"
          placeholder="Title"
          value={title}
          onChange={(e) => setTitle(e.target.value)}
        />
        <textarea
          placeholder="Content"
          value={content}
          onChange={(e) => setContent(e.target.value)}
        ></textarea>
        <button type="submit">Create Announcement</button>
      </form>
      <ul>
        {data.announcements.map((ann) => (
          <li key={ann.id}>
            <strong>{ann.title}</strong> - {ann.content} <em>at {ann.createdAt}</em>
          </li>
        ))}
      </ul>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

GraphQL Schema

Define your GraphQL schema to match the resolver functions:

type User {
  id: ID!
  username: String!
}

type Message {
  id: ID!
  content: String!
  timestamp: String!
  sender: User!
  receiver: User!
}

type Announcement {
  id: ID!
  title: String!
  content: String!
  createdAt: String!
}

type Query {
  messages: [Message!]!
  announcements: [Announcement!]!
}

type Mutation {


  sendMessage(senderId: Int!, receiverId: Int!, content: String!): Message!
  createAnnouncement(title: String!, content: String!): Announcement!
}
Enter fullscreen mode Exit fullscreen mode

Email Notifications (Optional)

To send email notifications, you can integrate an email service provider like SendGrid or Nodemailer in your NestJS application.

Email Service:

// email.service.ts
import { Injectable } from '@nestjs/common';
import * as nodemailer from 'nodemailer';

@Injectable()
export class EmailService {
  private transporter;

  constructor() {
    this.transporter = nodemailer.createTransport({
      service: 'gmail',
      auth: {
        user: 'your-email@gmail.com',
        pass: 'your-email-password',
      },
    });
  }

  async sendEmail(to: string, subject: string, text: string) {
    const mailOptions = {
      from: 'your-email@gmail.com',
      to,
      subject,
      text,
    };

    await this.transporter.sendMail(mailOptions);
  }
}
Enter fullscreen mode Exit fullscreen mode

You can then inject this EmailService in your MessageService and AnnouncementService to send email notifications upon message or announcement creation.

This setup covers the backend and frontend code for developing a messaging system, email notifications, and announcements/notices. You can expand on this by adding more features, such as real-time notifications, message threading, and more.

To implement real-time notifications in the messaging and announcements system, we can use WebSockets. Here, I'll guide you through setting up real-time notifications using NestJS with WebSockets on the backend and integrating it with the Next.js frontend.

Backend (NestJS)

1. Install WebSocket Dependencies

First, install the WebSocket package for NestJS:

npm install @nestjs/websockets @nestjs/platform-socket.io
Enter fullscreen mode Exit fullscreen mode

2. Create WebSocket Gateway

Create a WebSocket gateway to handle real-time communication.

message.gateway.ts

import {
  WebSocketGateway,
  WebSocketServer,
  SubscribeMessage,
  MessageBody,
  ConnectedSocket,
  OnGatewayConnection,
  OnGatewayDisconnect,
} from '@nestjs/websockets';
import { Server, Socket } from 'socket.io';
import { MessageService } from './message.service';
import { Message } from './message.entity';

@WebSocketGateway()
export class MessageGateway implements OnGatewayConnection, OnGatewayDisconnect {
  @WebSocketServer() server: Server;

  constructor(private readonly messageService: MessageService) {}

  async handleConnection(socket: Socket) {
    console.log(`Client connected: ${socket.id}`);
  }

  async handleDisconnect(socket: Socket) {
    console.log(`Client disconnected: ${socket.id}`);
  }

  @SubscribeMessage('sendMessage')
  async handleSendMessage(@MessageBody() data: { senderId: number, receiverId: number, content: string }) {
    const message = await this.messageService.create(data.senderId, data.receiverId, data.content);
    this.server.emit('receiveMessage', message);
    return message;
  }
}
Enter fullscreen mode Exit fullscreen mode

announcement.gateway.ts

import {
  WebSocketGateway,
  WebSocketServer,
  SubscribeMessage,
  MessageBody,
  ConnectedSocket,
  OnGatewayConnection,
  OnGatewayDisconnect,
} from '@nestjs/websockets';
import { Server, Socket } from 'socket.io';
import { AnnouncementService } from './announcement.service';
import { Announcement } from './announcement.entity';

@WebSocketGateway()
export class AnnouncementGateway implements OnGatewayConnection, OnGatewayDisconnect {
  @WebSocketServer() server: Server;

  constructor(private readonly announcementService: AnnouncementService) {}

  async handleConnection(socket: Socket) {
    console.log(`Client connected: ${socket.id}`);
  }

  async handleDisconnect(socket: Socket) {
    console.log(`Client disconnected: ${socket.id}`);
  }

  @SubscribeMessage('createAnnouncement')
  async handleCreateAnnouncement(@MessageBody() data: { title: string, content: string }) {
    const announcement = await this.announcementService.create(data.title, data.content);
    this.server.emit('newAnnouncement', announcement);
    return announcement;
  }
}
Enter fullscreen mode Exit fullscreen mode

3. Update Module

Update your module to include the gateways:

app.module.ts

import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Message } from './message.entity';
import { MessageService } from './message.service';
import { MessageGateway } from './message.gateway';
import { Announcement } from './announcement.entity';
import { AnnouncementService } from './announcement.service';
import { AnnouncementGateway } from './announcement.gateway';

@Module({
  imports: [TypeOrmModule.forFeature([Message, Announcement])],
  providers: [MessageService, MessageGateway, AnnouncementService, AnnouncementGateway],
})
export class AppModule {}
Enter fullscreen mode Exit fullscreen mode

Frontend (Next.js)

1. Install Socket.io Client

Install the socket.io client for Next.js:

npm install socket.io-client
Enter fullscreen mode Exit fullscreen mode

2. Set Up WebSocket Connection

Set up the WebSocket connection and handle real-time events.

lib/socket.js

import { io } from 'socket.io-client';

const socket = io('http://localhost:3000');

export default socket;
Enter fullscreen mode Exit fullscreen mode

3. Update Messaging System Page

Update the messaging system page to use WebSocket for real-time notifications.

pages/messages.js

import { useState, useEffect } from 'react';
import { useQuery, gql } from '@apollo/client';
import socket from '../lib/socket';

const GET_MESSAGES = gql`
  query GetMessages {
    messages {
      id
      content
      timestamp
      sender {
        username
      }
      receiver {
        username
      }
    }
  }
`;

export default function Messages() {
  const { loading, error, data, refetch } = useQuery(GET_MESSAGES);
  const [messages, setMessages] = useState([]);
  const [senderId, setSenderId] = useState('');
  const [receiverId, setReceiverId] = useState('');
  const [content, setContent] = useState('');

  useEffect(() => {
    if (data) {
      setMessages(data.messages);
    }
  }, [data]);

  useEffect(() => {
    socket.on('receiveMessage', (message) => {
      setMessages((prevMessages) => [...prevMessages, message]);
    });

    return () => {
      socket.off('receiveMessage');
    };
  }, []);

  const handleSubmit = (e) => {
    e.preventDefault();
    socket.emit('sendMessage', { senderId: parseInt(senderId), receiverId: parseInt(receiverId), content });
    setSenderId('');
    setReceiverId('');
    setContent('');
  };

  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error: {error.message}</p>;

  return (
    <div>
      <h1>Messages</h1>
      <form onSubmit={handleSubmit}>
        <input
          type="number"
          placeholder="Sender ID"
          value={senderId}
          onChange={(e) => setSenderId(e.target.value)}
        />
        <input
          type="number"
          placeholder="Receiver ID"
          value={receiverId}
          onChange={(e) => setReceiverId(e.target.value)}
        />
        <textarea
          placeholder="Message Content"
          value={content}
          onChange={(e) => setContent(e.target.value)}
        ></textarea>
        <button type="submit">Send Message</button>
      </form>
      <ul>
        {messages.map((msg) => (
          <li key={msg.id}>
            <strong>{msg.sender.username}</strong> to <strong>{msg.receiver.username}</strong>: {msg.content} <em>at {msg.timestamp}</em>
          </li>
        ))}
      </ul>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

4. Update Announcements Page

Update the announcements page to use WebSocket for real-time notifications.

pages/announcements.js

import { useState, useEffect } from 'react';
import { useQuery, gql } from '@apollo/client';
import socket from '../lib/socket';

const GET_ANNOUNCEMENTS = gql`
  query GetAnnouncements {
    announcements {
      id
      title
      content
      createdAt
    }
  }
`;

export default function Announcements() {
  const { loading, error, data, refetch } = useQuery(GET_ANNOUNCEMENTS);
  const [announcements, setAnnouncements] = useState([]);
  const [title, setTitle] = useState('');
  const [content, setContent] = useState('');

  useEffect(() => {
    if (data) {
      setAnnouncements(data.announcements);
    }
  }, [data]);

  useEffect(() => {
    socket.on('newAnnouncement', (announcement) => {
      setAnnouncements((prevAnnouncements) => [...prevAnnouncements, announcement]);
    });

    return () => {
      socket.off('newAnnouncement');
    };
  }, []);

  const handleSubmit = (e) => {
    e.preventDefault();
    socket.emit('createAnnouncement', { title, content });
    setTitle('');
    setContent('');
  };

  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error: {error.message}</p>;

  return (
    <div>
      <h1>Announcements</h1>
      <form onSubmit={handleSubmit}>
        <input
          type="text"
          placeholder="Title"
          value={title}
          onChange={(e) => setTitle(e.target.value)}
        />
        <textarea
          placeholder="Content"
          value={content}
          onChange={(e) => setContent(e.target.value)}
        ></textarea>
        <button type="submit">Create Announcement</button>
      </form>
      <ul>
        {announcements.map((ann) => (
          <li key={ann.id}>
            <strong>{ann.title}</strong> - {ann.content} <em>at {ann.createdAt}</em>
          </li>
        ))}
      </ul>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Running the Application

  1. Start the NestJS server with WebSocket support.
  2. Start the Next.js application.
  3. Test sending messages and creating announcements to see real-time updates.

This setup provides real-time notifications for messaging and announcements using WebSockets, enhancing the user experience with immediate updates. You can expand this further by adding more features and optimizations as needed.

If you enjoy my content and would like to support my work, you can buy me a coffee. Your support is greatly appreciated!

Disclaimer: This content in generated by AI.

Top comments (0)