// server/index.ts
import express from 'express';
import http from 'http';
import { Server, Socket } from 'socket.io';
const app = express();
const server = http.createServer(app);
const io = new Server(server, {
cors: { origin: 'http://localhost:3000' },
});
interface Message {
user: string;
text: string;
}
interface SendMessagePayload {
room: string;
user: string;
text: string;
}
io.on('connection', (socket: Socket) => {
console.log('User connected:', socket.id);
socket.on('joinRoom', (room: string) => {
socket.join(room);
socket.broadcast.to(room).emit('message', {
user: 'System',
text: `User ${socket.id} joined!`,
});
});
socket.on('sendMessage', ({ room, user, text }: SendMessagePayload) => {
const message: Message = { user, text };
io.to(room).emit('message', message);
});
socket.on('disconnect', () => {
console.log('User disconnected:', socket.id);
});
});
server.listen(3000, () => console.log('Server running on port 3000'));
'use client';
import { useState, useEffect } from 'react';
import io, { Socket } from 'socket.io-client';
interface Message {
user: string;
text: string;
}
const socket: Socket = io('http://localhost:3000');
export default function Chat() {
const [room, setRoom] = useState<string>('study-group');
const [user, setUser] = useState<string>(
'Student' + Math.floor(Math.random() * 100),
);
const [text, setText] = useState<string>('');
const [messages, setMessages] = useState<Message[]>([]);
useEffect(() => {
socket.emit('joinRoom', room);
socket.on('message', (message: Message) => {
setMessages((prev) => [...prev, message]);
});
return () => {
socket.off('message');
};
}, [room]);
const sendMessage = () => {
if (text.trim()) {
socket.emit('sendMessage', { room, user, text });
setText('');
}
};
return (
<div className="p-4 max-w-md mx-auto">
<h1 className="text-2xl font-bold">Study Group Chat</h1>
<div className="border p-2 h-64 overflow-y-auto mb-2">
{messages.map((msg, i) => (
<div key={i}>
<strong>{msg.user}:</strong> {msg.text}
</div>
))}
</div>
<input
type="text"
value={text}
onChange={(e) => setText(e.target.value)}
className="border p-2 w-full"
placeholder="Type a message..."
/>
<button onClick={sendMessage}>
Send
</button>
</div>
);
}
// app/api/messages/stream/route.ts
import { NextRequest, NextResponse } from 'next/server';
interface Message {
user: string;
text: string;
}
let messages: Message[] = [];
let clients: TransformStreamDefaultController[] = [];
export async function GET() {
const stream = new ReadableStream({
start(controller) {
clients.push(controller);
controller.enqueue(`data: ${JSON.stringify(messages)}\n\n`);
const interval = setInterval(() => {
controller.enqueue(`data: ${JSON.stringify(messages)}\n\n`);
}, 1000);
return () => {
clearInterval(interval);
clients = clients.filter((c) => c !== controller);
};
},
});
return new NextResponse(stream, {
headers: {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
Connection: 'keep-alive',
},
});
}
export async function POST(request: NextRequest) {
const { user, text }: Message = await request.json();
messages.push({ user, text });
clients.forEach((client) =>
client.enqueue(`data: ${JSON.stringify(messages)}\n\n`),
);
return NextResponse.json({ success: true });
}
// app/page.tsx
'use client';
import { useState, useEffect } from 'react';
interface Message {
user: string;
text: string;
}
export default function Chat() {
const [user, setUser] = useState<string>(
'Student' + Math.floor(Math.random() * 100),
);
const [text, setText] = useState<string>('');
const [messages, setMessages] = useState<Message[]>([]);
useEffect(() => {
const eventSource = new EventSource('/api/messages/stream');
eventSource.onmessage = (event) => {
const newMessages: Message[] = JSON.parse(event.data);
setMessages(newMessages);
};
return () => eventSource.close();
}, []);
const sendMessage = async () => {
if (text.trim()) {
await fetch('/api/messages/stream', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ user, text }),
});
setText('');
}
};
return (
<div className="p-4 max-w-md mx-auto">
<h1 className="text-2xl font-bold">Study Group Chat (SSE)</h1>
<div className="border p-2 h-64 overflow-y-auto mb-2">
{messages.map((msg, i) => (
<div key={i}>
<strong>{msg.user}:</strong> {msg.text}
</div>
))}
</div>
<input
type="text"
value={text}
onChange={(e) => setText(e.target.value)}
className="border p-2 w-full"
placeholder="Type a message..."
/>
<button onClick={sendMessage} className="bg-blue-500 text-white p-2 mt-2">
Send
</button>
</div>
);
}