
In this post, we'll walk through the process of building a real-time chat application using Next.js, TypeScript, Prisma, and Pusher. We'll explain the purpose of each package, how they work together, and provide a step-by-step explanation of the code.
Real-time communication is a crucial feature in modern web applications, especially for chat applications, notifications, and collaborative tools. In this tutorial, we'll build a real-time chat application where users can send and receive messages instantly. We'll use Next.js for the frontend and backend, TypeScript for type safety, Prisma for database management, and Pusher for real-time communication.
Next.js is a React framework that enables server-side rendering, static site generation, and API routes. It simplifies the development of full-stack applications by providing a unified structure for both frontend and backend.
TypeScript is a typed superset of JavaScript that adds static type checking. It helps catch errors during development and improves code readability.
Prisma is an ORM (Object-Relational Mapping) tool that simplifies database access and management. It provides a type-safe API for interacting with your database.
Pusher is a real-time communication service that enables instant data transfer between clients and servers. It uses WebSockets to provide low-latency communication.
Run the following command to create a new Next.js project:
npx create-next-app@latest real-time-chat --typescript
Install the required dependencies:
npm install pusher pusher-js prisma @prisma/client zod axios
Initialize Prisma and configure your database:
npx prisma init
Update the schema.prisma file with your database configuration and define the models:
model Conversation { id String @id @default(auto()) @map("_id") @db.ObjectId name String email String @unique createdAt DateTime @default(now()) updatedAt DateTime @updatedAt Message Message[] } model Message { id String @id @default(auto()) @map("_id") @db.ObjectId message String conversationId String @db.ObjectId createdAt DateTime @default(now()) updatedAt DateTime @updatedAt userRole String @default("user") conversation Conversation @relation(fields: [conversationId], references: [id]) }
Run the following command to generate and apply the migrations:
npx prisma migrate dev --name init
Create a pusher.ts file to configure Pusher for both server and client:
import PusherServer from "pusher"; import Pusher from "pusher-js"; export const pusherServer = new PusherServer({ appId: process.env.PUSHER_APPID as string, key: process.env.NEXT_PUBLIC_PUSHER_KEY as string, secret: process.env.PUSHER_SECRET as string, cluster: process.env.NEXT_PUBLIC_PUSHER_CLUSTER as string, useTLS: true, }); export const pusherClient = new Pusher( process.env.NEXT_PUBLIC_PUSHER_KEY as string, { cluster: process.env.NEXT_PUBLIC_PUSHER_CLUSTER as string, }, );
Create an API route to handle message creation and retrieval:
import { routeErrorHandler } from "@/lib/api-error-handler"; import { formatResponse } from "@/lib/api-response"; import { db } from "@/lib/db"; import { pusherServer } from "@/lib/pusher"; import { z } from "zod"; export async function POST(req: Request, { params }: { params: { id: string } }) { try { const { id } = params; const body = await req.json(); const { message, userRole } = body; const newMessage = await db.message.create({ data: { message, userRole, conversationId: id }, }); await pusherServer.trigger("my-channel", id, { messages: [newMessage], }); return formatResponse([newMessage], "Message sent successfully", 200); } catch (error) { return routeErrorHandler(error); } }
Subscribe to the Pusher channel and listen for new messages:
import { useEffect, useState } from "react"; import { pusherClient } from "@/lib/pusher"; const ChatComponent = ({ conversationId }: { conversationId: string }) => { const [messages, setMessages] = useState([]); useEffect(() => { const channel = pusherClient.subscribe("my-channel"); channel.bind(conversationId, (data: { messages: any[] }) => { setMessages((prev) => [...prev, ...data.messages]); }); return () => { channel.unbind(conversationId); pusherClient.unsubscribe("my-channel"); }; }, [conversationId]); return ( <div> {messages.map((msg, i) => ( <div key={i}>{msg.message}</div> ))} </div> ); };
Create a form to send messages:
import axios from "axios"; import { useState } from "react"; const MessageForm = ({ conversationId }: { conversationId: string }) => { const [message, setMessage] = useState(""); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); await axios.post(`/api/conversation/${conversationId}`, { message, userRole: "user", }); setMessage(""); }; return ( <form onSubmit={handleSubmit}> <input type="text" value={message} onChange={(e) => setMessage(e.target.value)} /> <button type="submit">Send</button> </form> ); };
Pusher enables real-time communication by using WebSockets. When a message is sent, the server triggers an event on a specific channel. The client listens to this channel and updates the UI in real time.
In this tutorial, we built a real-time chat application using Next.js, TypeScript, Prisma, and Pusher. We covered the setup, backend implementation, frontend integration, and real-time communication. This stack provides a robust and scalable solution for building real-time applications.
Feel free to extend this project by adding features like user authentication, message history, or multimedia support.
Comments
No Comments
Leave a replay
Your email address will not be publish. Required fields are marked *