Build Your First Chat Application
In this tutorial, you’ll learn how to build a production-ready chat application using Azure AI Foundry. We’ll create a web application with conversation memory, user authentication, and safety controls.
What you’ll build
By completing this tutorial, you will have created:
- A multi-user chat application with persistent conversations
- User authentication and session management
- Conversation memory that maintains context across messages
- Built-in content filtering and safety controls
- A responsive web interface that works on desktop and mobile
Prerequisites
Before starting, ensure you have:
- Completed the Getting Started tutorial
- An Azure AI Foundry project with a deployed chat model
- Node.js 18+ installed on your machine
- Basic familiarity with JavaScript and web development
Learning objectives
Through this hands-on tutorial, you will learn:
- How to build stateful chat applications with conversation memory
- Implementing user authentication in AI applications
- Designing responsive chat interfaces
- Adding safety controls and content moderation
- Deploying your application to production
Step 1: Set up your development environment
Let’s start by creating a new project and installing the required dependencies.
# Create a new project directory
mkdir foundry-chat-app
cd foundry-chat-app
# Initialize a new Node.js project
npm init -y
# Install required dependencies
npm install express socket.io openai uuid bcryptjs jsonwebtoken dotenv
npm install --save-dev nodemon
Create your project structure:
foundry-chat-app/
├── server.js
├── public/
│ ├── index.html
│ ├── chat.html
│ ├── style.css
│ └── script.js
├── .env
└── package.json
Create a .env file with your Azure AI Foundry credentials:
# Azure AI Foundry Configuration
AZURE_ENDPOINT=https://your-endpoint.inference.ml.azure.com
AZURE_API_KEY=your-api-key-here
AZURE_API_VERSION=2024-02-01
# Application Configuration
JWT_SECRET=your-jwt-secret-here
PORT=3000
Never commit your .env file to version control. Add it to your .gitignore file.
Step 3: Build the server foundation
Create server.js with the basic Express server setup:
const express = require('express');
const http = require('http');
const socketIo = require('socket.io');
const { AzureOpenAI } = require('openai');
const jwt = require('jsonwebtoken');
const bcrypt = require('bcryptjs');
const { v4: uuidv4 } = require('uuid');
require('dotenv').config();
const app = express();
const server = http.createServer(app);
const io = socketIo(server);
// Initialize Azure OpenAI client
const openai = new AzureOpenAI({
apiKey: process.env.AZURE_API_KEY,
apiVersion: process.env.AZURE_API_VERSION,
endpoint: process.env.AZURE_ENDPOINT
});
// Middleware
app.use(express.json());
app.use(express.static('public'));
// In-memory storage (use a real database in production)
const users = new Map();
const conversations = new Map();
// User authentication
const hashPassword = async (password) => {
return await bcrypt.hash(password, 10);
};
const verifyPassword = async (password, hash) => {
return await bcrypt.compare(password, hash);
};
const generateToken = (userId) => {
return jwt.sign({ userId }, process.env.JWT_SECRET, { expiresIn: '24h' });
};
const verifyToken = (token) => {
try {
return jwt.verify(token, process.env.JWT_SECRET);
} catch (error) {
return null;
}
};
// Routes
app.post('/api/register', async (req, res) => {
const { username, password } = req.body;
if (users.has(username)) {
return res.status(400).json({ error: 'Username already exists' });
}
const hashedPassword = await hashPassword(password);
const userId = uuidv4();
users.set(username, {
id: userId,
username,
password: hashedPassword,
createdAt: new Date()
});
const token = generateToken(userId);
res.json({ token, userId, username });
});
app.post('/api/login', async (req, res) => {
const { username, password } = req.body;
const user = users.get(username);
if (!user || !(await verifyPassword(password, user.password))) {
return res.status(401).json({ error: 'Invalid credentials' });
}
const token = generateToken(user.id);
res.json({ token, userId: user.id, username: user.username });
});
// Start server
const PORT = process.env.PORT || 3000;
server.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
Notice how we’ve set up:
- Authentication with JWT tokens
- In-memory storage for users and conversations
- Azure OpenAI client initialization
- Basic Express server structure
Step 4: Implement conversation management
Add conversation management to your server:
// Add this after the authentication functions
class ConversationManager {
constructor() {
this.conversations = new Map();
}
createConversation(userId) {
const conversationId = uuidv4();
const conversation = {
id: conversationId,
userId,
messages: [],
createdAt: new Date(),
updatedAt: new Date()
};
this.conversations.set(conversationId, conversation);
return conversationId;
}
getConversation(conversationId) {
return this.conversations.get(conversationId);
}
getUserConversations(userId) {
return Array.from(this.conversations.values())
.filter(conv => conv.userId === userId)
.sort((a, b) => b.updatedAt - a.updatedAt);
}
addMessage(conversationId, message) {
const conversation = this.conversations.get(conversationId);
if (!conversation) return false;
conversation.messages.push({
id: uuidv4(),
...message,
timestamp: new Date()
});
conversation.updatedAt = new Date();
return true;
}
getMessages(conversationId, limit = 50) {
const conversation = this.conversations.get(conversationId);
if (!conversation) return [];
return conversation.messages
.slice(-limit)
.map(msg => ({
id: msg.id,
role: msg.role,
content: msg.content,
timestamp: msg.timestamp
}));
}
}
const conversationManager = new ConversationManager();
// Add conversation routes
app.get('/api/conversations', (req, res) => {
const authHeader = req.headers.authorization;
if (!authHeader) {
return res.status(401).json({ error: 'No token provided' });
}
const token = authHeader.split(' ')[1];
const decoded = verifyToken(token);
if (!decoded) {
return res.status(401).json({ error: 'Invalid token' });
}
const conversations = conversationManager.getUserConversations(decoded.userId);
res.json(conversations.map(conv => ({
id: conv.id,
createdAt: conv.createdAt,
updatedAt: conv.updatedAt,
messageCount: conv.messages.length,
lastMessage: conv.messages[conv.messages.length - 1]?.content?.substring(0, 100) || ''
})));
});
app.post('/api/conversations', (req, res) => {
const authHeader = req.headers.authorization;
const token = authHeader?.split(' ')[1];
const decoded = verifyToken(token);
if (!decoded) {
return res.status(401).json({ error: 'Invalid token' });
}
const conversationId = conversationManager.createConversation(decoded.userId);
res.json({ conversationId });
});
Step 5: Add real-time chat functionality
Now let’s add Socket.IO for real-time messaging:
// Add this after the conversation routes
// Socket.IO authentication middleware
io.use((socket, next) => {
const token = socket.handshake.auth.token;
const decoded = verifyToken(token);
if (!decoded) {
return next(new Error('Authentication error'));
}
socket.userId = decoded.userId;
next();
});
// Socket.IO connection handling
io.on('connection', (socket) => {
console.log(`User ${socket.userId} connected`);
socket.on('join-conversation', (conversationId) => {
// Verify user owns this conversation
const conversation = conversationManager.getConversation(conversationId);
if (!conversation || conversation.userId !== socket.userId) {
socket.emit('error', 'Unauthorized');
return;
}
socket.join(conversationId);
// Send conversation history
const messages = conversationManager.getMessages(conversationId);
socket.emit('conversation-history', messages);
});
socket.on('send-message', async (data) => {
const { conversationId, message } = data;
// Verify conversation ownership
const conversation = conversationManager.getConversation(conversationId);
if (!conversation || conversation.userId !== socket.userId) {
socket.emit('error', 'Unauthorized');
return;
}
try {
// Add user message to conversation
const userMessage = {
role: 'user',
content: message
};
conversationManager.addMessage(conversationId, userMessage);
// Emit user message to the conversation
socket.to(conversationId).emit('new-message', {
id: uuidv4(),
role: 'user',
content: message,
timestamp: new Date()
});
// Get conversation history for AI context
const messages = conversationManager.getMessages(conversationId);
// Call Azure OpenAI
const response = await openai.chat.completions.create({
messages: messages.map(msg => ({
role: msg.role,
content: msg.content
})),
max_tokens: 500,
temperature: 0.7,
stream: true
});
let assistantMessage = '';
// Stream the response
for await (const chunk of response) {
const content = chunk.choices[0]?.delta?.content || '';
if (content) {
assistantMessage += content;
socket.emit('message-chunk', {
conversationId,
content: content
});
}
}
// Add complete assistant message to conversation
const completeAssistantMessage = {
role: 'assistant',
content: assistantMessage
};
conversationManager.addMessage(conversationId, completeAssistantMessage);
// Emit complete message
socket.emit('message-complete', {
id: uuidv4(),
role: 'assistant',
content: assistantMessage,
timestamp: new Date()
});
} catch (error) {
console.error('Error generating response:', error);
socket.emit('error', 'Failed to generate response');
}
});
socket.on('disconnect', () => {
console.log(`User ${socket.userId} disconnected`);
});
});
Notice how we’ve implemented:
- Real-time message streaming
- Conversation history management
- User authorization for conversations
- Error handling for AI generation
Step 6: Create the frontend
Create public/index.html for the login/register page:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Foundry Chat - Login</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="container">
<div class="auth-card">
<h1>Foundry Chat</h1>
<div class="auth-tabs">
<button class="tab-button active" data-tab="login">Login</button>
<button class="tab-button" data-tab="register">Register</button>
</div>
<form id="login-form" class="auth-form active">
<input type="text" id="login-username" placeholder="Username" required>
<input type="password" id="login-password" placeholder="Password" required>
<button type="submit">Login</button>
</form>
<form id="register-form" class="auth-form">
<input type="text" id="register-username" placeholder="Username" required>
<input type="password" id="register-password" placeholder="Password" required>
<button type="submit">Register</button>
</form>
<div id="auth-error" class="error"></div>
</div>
</div>
<script src="auth.js"></script>
</body>
</html>
Create public/chat.html for the main chat interface:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Foundry Chat</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="chat-container">
<div class="sidebar">
<div class="sidebar-header">
<h2>Conversations</h2>
<button id="new-conversation">+</button>
</div>
<div class="conversations-list" id="conversations-list">
<!-- Conversations will be loaded here -->
</div>
<div class="user-info">
<span id="username"></span>
<button id="logout">Logout</button>
</div>
</div>
<div class="chat-area">
<div class="chat-header">
<h3 id="conversation-title">Select a conversation</h3>
</div>
<div class="messages-container" id="messages-container">
<!-- Messages will appear here -->
</div>
<div class="input-area">
<input type="text" id="message-input" placeholder="Type your message..." disabled>
<button id="send-button" disabled>Send</button>
</div>
</div>
</div>
<script src="/socket.io/socket.io.js"></script>
<script src="chat.js"></script>
</body>
</html>
Step 7: Style your application
Create public/style.css:
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background-color: #f5f5f5;
height: 100vh;
}
/* Authentication Styles */
.container {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
}
.auth-card {
background: white;
padding: 2rem;
border-radius: 12px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
width: 100%;
max-width: 400px;
}
.auth-card h1 {
text-align: center;
margin-bottom: 2rem;
color: #2563eb;
}
.auth-tabs {
display: flex;
margin-bottom: 1.5rem;
}
.tab-button {
flex: 1;
padding: 0.75rem;
border: none;
background: #f3f4f6;
cursor: pointer;
border-radius: 6px 6px 0 0;
}
.tab-button.active {
background: #2563eb;
color: white;
}
.auth-form {
display: none;
}
.auth-form.active {
display: block;
}
.auth-form input {
width: 100%;
padding: 0.75rem;
margin-bottom: 1rem;
border: 1px solid #d1d5db;
border-radius: 6px;
font-size: 1rem;
}
.auth-form button {
width: 100%;
padding: 0.75rem;
background: #2563eb;
color: white;
border: none;
border-radius: 6px;
font-size: 1rem;
cursor: pointer;
}
.auth-form button:hover {
background: #1d4ed8;
}
.error {
color: #ef4444;
text-align: center;
margin-top: 1rem;
}
/* Chat Interface Styles */
.chat-container {
display: flex;
height: 100vh;
}
.sidebar {
width: 300px;
background: white;
border-right: 1px solid #e5e7eb;
display: flex;
flex-direction: column;
}
.sidebar-header {
padding: 1rem;
border-bottom: 1px solid #e5e7eb;
display: flex;
justify-content: space-between;
align-items: center;
}
.sidebar-header h2 {
font-size: 1.25rem;
color: #374151;
}
#new-conversation {
width: 32px;
height: 32px;
border-radius: 50%;
border: none;
background: #2563eb;
color: white;
cursor: pointer;
font-size: 1.25rem;
}
.conversations-list {
flex: 1;
overflow-y: auto;
}
.conversation-item {
padding: 1rem;
border-bottom: 1px solid #f3f4f6;
cursor: pointer;
transition: background-color 0.2s;
}
.conversation-item:hover {
background: #f9fafb;
}
.conversation-item.active {
background: #eff6ff;
border-right: 3px solid #2563eb;
}
.user-info {
padding: 1rem;
border-top: 1px solid #e5e7eb;
display: flex;
justify-content: space-between;
align-items: center;
}
#logout {
padding: 0.5rem 1rem;
background: #ef4444;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
.chat-area {
flex: 1;
display: flex;
flex-direction: column;
}
.chat-header {
padding: 1rem;
background: white;
border-bottom: 1px solid #e5e7eb;
}
.messages-container {
flex: 1;
overflow-y: auto;
padding: 1rem;
background: #f9fafb;
}
.message {
margin-bottom: 1rem;
display: flex;
align-items: flex-start;
}
.message.user {
justify-content: flex-end;
}
.message-content {
max-width: 70%;
padding: 0.75rem 1rem;
border-radius: 12px;
line-height: 1.5;
}
.message.user .message-content {
background: #2563eb;
color: white;
}
.message.assistant .message-content {
background: white;
border: 1px solid #e5e7eb;
}
.input-area {
padding: 1rem;
background: white;
border-top: 1px solid #e5e7eb;
display: flex;
gap: 0.5rem;
}
#message-input {
flex: 1;
padding: 0.75rem;
border: 1px solid #d1d5db;
border-radius: 6px;
font-size: 1rem;
}
#send-button {
padding: 0.75rem 1.5rem;
background: #2563eb;
color: white;
border: none;
border-radius: 6px;
cursor: pointer;
}
#send-button:disabled {
background: #9ca3af;
cursor: not-allowed;
}
/* Responsive Design */
@media (max-width: 768px) {
.chat-container {
flex-direction: column;
}
.sidebar {
width: 100%;
height: auto;
order: 2;
}
.chat-area {
order: 1;
height: 70vh;
}
}
Step 8: Add JavaScript functionality
Create public/auth.js for authentication:
document.addEventListener('DOMContentLoaded', function() {
const tabButtons = document.querySelectorAll('.tab-button');
const authForms = document.querySelectorAll('.auth-form');
const loginForm = document.getElementById('login-form');
const registerForm = document.getElementById('register-form');
const authError = document.getElementById('auth-error');
// Tab switching
tabButtons.forEach(button => {
button.addEventListener('click', () => {
const tab = button.dataset.tab;
tabButtons.forEach(btn => btn.classList.remove('active'));
authForms.forEach(form => form.classList.remove('active'));
button.classList.add('active');
document.getElementById(`${tab}-form`).classList.add('active');
authError.textContent = '';
});
});
// Login form
loginForm.addEventListener('submit', async (e) => {
e.preventDefault();
const username = document.getElementById('login-username').value;
const password = document.getElementById('login-password').value;
try {
const response = await fetch('/api/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ username, password })
});
const data = await response.json();
if (response.ok) {
localStorage.setItem('token', data.token);
localStorage.setItem('username', data.username);
window.location.href = '/chat.html';
} else {
authError.textContent = data.error;
}
} catch (error) {
authError.textContent = 'Login failed. Please try again.';
}
});
// Register form
registerForm.addEventListener('submit', async (e) => {
e.preventDefault();
const username = document.getElementById('register-username').value;
const password = document.getElementById('register-password').value;
if (password.length < 6) {
authError.textContent = 'Password must be at least 6 characters long.';
return;
}
try {
const response = await fetch('/api/register', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ username, password })
});
const data = await response.json();
if (response.ok) {
localStorage.setItem('token', data.token);
localStorage.setItem('username', data.username);
window.location.href = '/chat.html';
} else {
authError.textContent = data.error;
}
} catch (error) {
authError.textContent = 'Registration failed. Please try again.';
}
});
});
Create public/chat.js for the chat functionality:
class ChatApp {
constructor() {
this.socket = null;
this.currentConversationId = null;
this.token = localStorage.getItem('token');
this.username = localStorage.getItem('username');
if (!this.token) {
window.location.href = '/';
return;
}
this.initializeElements();
this.connectSocket();
this.loadConversations();
this.setupEventListeners();
}
initializeElements() {
this.usernameDisplay = document.getElementById('username');
this.conversationsList = document.getElementById('conversations-list');
this.messagesContainer = document.getElementById('messages-container');
this.messageInput = document.getElementById('message-input');
this.sendButton = document.getElementById('send-button');
this.newConversationButton = document.getElementById('new-conversation');
this.logoutButton = document.getElementById('logout');
this.conversationTitle = document.getElementById('conversation-title');
this.usernameDisplay.textContent = this.username;
}
connectSocket() {
this.socket = io({
auth: {
token: this.token
}
});
this.socket.on('connect', () => {
console.log('Connected to server');
});
this.socket.on('conversation-history', (messages) => {
this.displayMessages(messages);
});
this.socket.on('new-message', (message) => {
this.displayMessage(message);
});
this.socket.on('message-chunk', (data) => {
this.appendToLastMessage(data.content);
});
this.socket.on('message-complete', (message) => {
this.finalizeLastMessage(message);
});
this.socket.on('error', (error) => {
console.error('Socket error:', error);
alert('Connection error: ' + error);
});
this.socket.on('disconnect', () => {
console.log('Disconnected from server');
});
}
async loadConversations() {
try {
const response = await fetch('/api/conversations', {
headers: {
'Authorization': `Bearer ${this.token}`
}
});
if (response.ok) {
const conversations = await response.json();
this.displayConversations(conversations);
} else {
console.error('Failed to load conversations');
}
} catch (error) {
console.error('Error loading conversations:', error);
}
}
displayConversations(conversations) {
this.conversationsList.innerHTML = '';
conversations.forEach(conversation => {
const conversationElement = document.createElement('div');
conversationElement.className = 'conversation-item';
conversationElement.dataset.conversationId = conversation.id;
conversationElement.innerHTML = `
<div class="conversation-preview">
<div class="conversation-time">${new Date(conversation.updatedAt).toLocaleDateString()}</div>
<div class="conversation-snippet">${conversation.lastMessage || 'New conversation'}</div>
</div>
`;
conversationElement.addEventListener('click', () => {
this.selectConversation(conversation.id);
});
this.conversationsList.appendChild(conversationElement);
});
}
selectConversation(conversationId) {
// Update UI
document.querySelectorAll('.conversation-item').forEach(item => {
item.classList.remove('active');
});
const selectedItem = document.querySelector(`[data-conversation-id="${conversationId}"]`);
if (selectedItem) {
selectedItem.classList.add('active');
}
// Enable input
this.messageInput.disabled = false;
this.sendButton.disabled = false;
this.conversationTitle.textContent = 'Active Conversation';
// Join conversation
this.currentConversationId = conversationId;
this.socket.emit('join-conversation', conversationId);
// Clear messages
this.messagesContainer.innerHTML = '';
}
async createNewConversation() {
try {
const response = await fetch('/api/conversations', {
method: 'POST',
headers: {
'Authorization': `Bearer ${this.token}`
}
});
if (response.ok) {
const data = await response.json();
this.selectConversation(data.conversationId);
this.loadConversations(); // Refresh the list
} else {
console.error('Failed to create conversation');
}
} catch (error) {
console.error('Error creating conversation:', error);
}
}
setupEventListeners() {
this.sendButton.addEventListener('click', () => {
this.sendMessage();
});
this.messageInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
this.sendMessage();
}
});
this.newConversationButton.addEventListener('click', () => {
this.createNewConversation();
});
this.logoutButton.addEventListener('click', () => {
localStorage.removeItem('token');
localStorage.removeItem('username');
window.location.href = '/';
});
}
sendMessage() {
const message = this.messageInput.value.trim();
if (!message || !this.currentConversationId) return;
// Display user message immediately
this.displayMessage({
role: 'user',
content: message,
timestamp: new Date()
});
// Send to server
this.socket.emit('send-message', {
conversationId: this.currentConversationId,
message: message
});
// Clear input
this.messageInput.value = '';
// Show typing indicator
this.showTypingIndicator();
}
displayMessages(messages) {
this.messagesContainer.innerHTML = '';
messages.forEach(message => {
this.displayMessage(message);
});
this.scrollToBottom();
}
displayMessage(message) {
const messageElement = document.createElement('div');
messageElement.className = `message ${message.role}`;
messageElement.innerHTML = `
<div class="message-content">
${this.formatMessage(message.content)}
</div>
`;
this.messagesContainer.appendChild(messageElement);
this.scrollToBottom();
}
showTypingIndicator() {
const typingElement = document.createElement('div');
typingElement.className = 'message assistant typing';
typingElement.id = 'typing-indicator';
typingElement.innerHTML = `
<div class="message-content">
<div class="typing-animation">
<span></span>
<span></span>
<span></span>
</div>
</div>
`;
this.messagesContainer.appendChild(typingElement);
this.scrollToBottom();
}
appendToLastMessage(content) {
const typingIndicator = document.getElementById('typing-indicator');
if (typingIndicator) {
typingIndicator.remove();
}
let lastMessage = this.messagesContainer.lastElementChild;
if (!lastMessage || !lastMessage.classList.contains('assistant')) {
// Create new assistant message
lastMessage = document.createElement('div');
lastMessage.className = 'message assistant streaming';
lastMessage.innerHTML = '<div class="message-content"></div>';
this.messagesContainer.appendChild(lastMessage);
}
const contentElement = lastMessage.querySelector('.message-content');
contentElement.textContent += content;
this.scrollToBottom();
}
finalizeLastMessage(message) {
const lastMessage = this.messagesContainer.lastElementChild;
if (lastMessage && lastMessage.classList.contains('streaming')) {
lastMessage.classList.remove('streaming');
const contentElement = lastMessage.querySelector('.message-content');
contentElement.innerHTML = this.formatMessage(message.content);
}
this.scrollToBottom();
this.loadConversations(); // Refresh conversation list
}
formatMessage(content) {
// Basic markdown formatting
return content
.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
.replace(/\*(.*?)\*/g, '<em>$1</em>')
.replace(/`(.*?)`/g, '<code>$1</code>')
.replace(/\n/g, '<br>');
}
scrollToBottom() {
this.messagesContainer.scrollTop = this.messagesContainer.scrollHeight;
}
}
// Initialize the chat app when the page loads
document.addEventListener('DOMContentLoaded', () => {
new ChatApp();
});
Step 9: Test your application
Now let’s test your complete chat application:
-
Start your server:
npm run dev
# or
node server.js
-
Open your browser and navigate to
http://localhost:3000
-
Create a new account by clicking “Register” and filling in your details
-
Once logged in, you should see the chat interface
-
Click the ”+” button to create a new conversation
-
Type a message and press Enter
You should see:
- Your message appears immediately
- A typing indicator shows while the AI is thinking
- The AI response streams in real-time
- Conversations are saved and can be accessed later
What you’ve accomplished
Congratulations! You’ve built a complete chat application with:
✅ User authentication - Secure login and registration
✅ Real-time messaging - Instant message delivery with Socket.IO
✅ Conversation memory - Chat history preserved across sessions
✅ Streaming responses - AI messages appear as they’re generated
✅ Responsive design - Works on desktop and mobile devices
✅ Production patterns - Error handling, authentication, and security
Key concepts you’ve learned
Through building this application, you’ve learned:
State management: How to maintain conversation history and user sessions in AI applications
Real-time communication: Using WebSockets for instant message delivery and streaming AI responses
Authentication patterns: Implementing secure user authentication in AI applications
UI/UX for AI: Creating interfaces that handle the unique challenges of AI-generated content
Error handling: Managing network issues, AI generation failures, and user errors
Next steps
Your chat application is functional, but there’s more you can add:
Troubleshooting
Messages not sending?
- Check that your Azure AI Foundry endpoint and API key are correct
- Verify the model deployment is active and healthy
- Check browser console for error messages
Authentication issues?
- Ensure JWT_SECRET is set in your .env file
- Check that tokens are being stored in localStorage
- Verify token expiration settings
Real-time features not working?
- Confirm Socket.IO is properly connected
- Check for firewall or proxy issues
- Verify WebSocket support in your browser
Production considerations
Before deploying this application to production:
- Use a real database instead of in-memory storage
- Implement rate limiting to prevent abuse
- Add input validation and sanitization
- Set up proper logging and monitoring
- Configure HTTPS and secure headers
- Implement backup and recovery procedures
This tutorial focused on the practical steps of building a chat application. You learned through doing - implementing authentication, managing state, handling real-time communication, and creating user interfaces. The conceptual understanding of these patterns will serve you well in building more complex AI applications.