API Reference
I built this chat application using Firebase's Realtime Database as the backend. Here's a detailed reference of the API structure and the key functions I implemented.
Database Structure
The database is organized into several main collections:
Users Collection
/users/{userId}: {
"displayName": "John Doe",
"email": "john@example.com",
"photoURL": "https://example.com/photo.jpg",
"bio": "Hello, I'm John!",
"isOnline": true,
"lastSeen": 1650120000000,
"createdAt": 1649120000000,
"isAdmin": false,
"blockedUsers": {
"userId1": true,
"userId2": true
}
}
Chats Collection
/chats/{chatId}: {
"type": "private", // or "group"
"createdAt": 1649120000000,
"updatedAt": 1650120000000,
"lastMessageTime": 1650120050000,
// For private chats
"participants": {
"userId1": true,
"userId2": true
},
// For group chats
"name": "Project Team",
"createdBy": "userId1",
"members": {
"userId1": {
"role": "admin",
"joinedAt": 1649120000000
},
"userId2": {
"role": "member",
"joinedAt": 1649120010000
}
},
// Last message preview (for both types)
"lastMessage": {
"content": "Hello everyone!",
"sender": "userId1",
"senderName": "John Doe",
"timestamp": 1650120050000
}
}
Messages Collection
/messages/{chatId}/{messageId}: {
"content": "Hello, how are you?",
"sender": "userId1",
"senderName": "John Doe",
"senderPhotoURL": "https://example.com/photo.jpg",
"timestamp": 1650120000000,
"edited": false,
"editedAt": null,
"deleted": false,
"deletedAt": null,
"type": "text", // or "file", "voice"
"readBy": {
"userId1": 1650120000000,
"userId2": 1650120010000
},
"replyTo": "messageId123", // Optional reference to another message
// For file messages
"fileName": "document.pdf",
"fileSize": 1024000,
"fileType": "application/pdf",
"fileCategory": "document",
"fileData": "base64string...",
// For voice messages
"voiceData": "base64string...",
"duration": 15, // in seconds
// Reactions
"reactions": {
"userId1": "👍",
"userId2": "❤️"
}
}
User Chats Collection
This is a mapping between users and their chats for quick access:
/userChats/{userId}/{chatId}: {
"timestamp": 1650120000000,
"unreadCount": 5
}
Typing Indicators Collection
/typing/{chatId}/{userId}: {
"isTyping": true,
"displayName": "John Doe",
"timestamp": 1650120000000
}
Logs Collection
/logs/{chatId}/{logId}: {
"messageId": "messageId123",
"type": "EDIT", // or "DELETE"
"performedBy": "userId1",
"performedByName": "John Doe",
"timestamp": 1650120050000,
"originalContent": "Hello there",
"newContent": "Hello everyone" // Only for EDIT logs
}
Core API Functions
Authentication
// Sign up a new user
const signup = async (email, password, displayName) => {
const userCredential = await createUserWithEmailAndPassword(auth, email, password);
await updateProfile(userCredential.user, { displayName });
await set(ref(db, `users/${userCredential.user.uid}`), {
displayName,
email,
createdAt: serverTimestamp(),
lastSeen: serverTimestamp(),
isOnline: true
});
return userCredential.user;
};
// Sign in an existing user
const login = async (email, password) => {
return signInWithEmailAndPassword(auth, email, password);
};
// Sign out
const logout = async () => {
// Update user status before signing out
if (user) {
await update(ref(db, `users/${user.uid}`), {
isOnline: false,
lastSeen: serverTimestamp()
});
}
return signOut(auth);
};
// Reset password
const resetPassword = (email) => {
return sendPasswordResetEmail(auth, email);
};
// Update user profile
const updateUserProfile = async (data) => {
if (!user) return;
// Update Firebase Auth profile
await updateProfile(user, {
displayName: data.displayName || user.displayName,
photoURL: data.photoURL || user.photoURL
});
// Update database user data
await update(ref(db, `users/${user.uid}`), {
displayName: data.displayName || user.displayName,
photoURL: data.photoURL || user.photoURL,
bio: data.bio || null,
updatedAt: serverTimestamp()
});
};
Chat Management
// Create a direct chat between two users
const createChat = async (otherUserId) => {
// Implementation details in ChatContext section
};
// Create a group chat
const createGroupChat = async (name, members) => {
// Implementation details in ChatContext section
};
// Leave a chat
const leaveChat = async (chatId) => {
if (!chatId || !user?.uid) return false;
try {
const chatRef = ref(db, `chats/${chatId}`);
const snapshot = await get(chatRef);
if (!snapshot.exists()) {
throw new Error('Chat does not exist');
}
const chatData = snapshot.val();
if (chatData.type === 'private') {
// For private chats, remove the chat reference from userChats
await set(ref(db, `userChats/${user.uid}/${chatId}`), null);
} else if (chatData.type === 'group') {
// For group chats, remove the user from members
await update(ref(db, `chats/${chatId}/members/${user.uid}`), null);
await set(ref(db, `userChats/${user.uid}/${chatId}`), null);
}
return true;
} catch (error) {
console.error('Error leaving chat:', error);
return false;
}
};
// Update chat metadata (for group chats)
const updateChatMeta = async (chatId, data) => {
if (!chatId || !user?.uid) return false;
try {
// Check if user has permission (is admin)
const chatRef = ref(db, `chats/${chatId}`);
const snapshot = await get(chatRef);
if (!snapshot.exists()) {
throw new Error('Chat does not exist');
}
const chatData = snapshot.val();
if (
chatData.type !== 'group' ||
!chatData.members?.[user.uid]?.role === 'admin'
) {
throw new Error('Not authorized to update this chat');
}
// Update allowed fields
const updates = {};
if (data.name) updates.name = data.name;
if (data.photoURL) updates.photoURL = data.photoURL;
updates.updatedAt = serverTimestamp();
await update(chatRef, updates);
return true;
} catch (error) {
console.error('Error updating chat:', error);
return false;
}
};
Message Operations
// Send a text message
const sendMessage = async (content, replyToId = null) => {
// Implementation details in ChatContext section
};
// Edit a message
const editMessage = async (chatId, messageId, newContent) => {
// Implementation details in ChatContext section
};
// Delete a message
const deleteMessage = async (chatId, messageId) => {
// Implementation details in ChatContext section
};
// Mark messages as read
const markChatAsRead = async (chatId) => {
// Implementation details in ChatContext section
};
// Add reaction to a message
const handleVote = async (chatId, messageId, reaction) => {
// Implementation details in ChatContext section
};
Error Handling
I implemented comprehensive error handling throughout the application:
// Example of error handling in message sending
try {
// Send message logic
} catch (error) {
// Check error type
if (error.code === 'PERMISSION_DENIED') {
// Handle permission errors
showToast('You do not have permission to send messages in this chat');
} else if (error.code === 'NETWORK_ERROR') {
// Handle network errors
queueMessageForLaterSending(message);
showToast('Message will be sent when you reconnect');
} else {
// Generic error handling
console.error('Error sending message:', error);
showToast('Failed to send message. Please try again.');
}
}
Performance Considerations
When building this API, I focused on several performance aspects:
- Pagination: Messages are loaded in batches of 20-50 to avoid loading the entire chat history at once
- Selective Updates: Using Firebase's update() method to modify only specific fields
- Indexing: Ensuring proper indexing in the database for common queries
- Optimistic UI Updates: Updating the UI before server confirmation to improve perceived performance
- Data Denormalization: Strategic duplication of data (like lastMessage in chats) to reduce query complexity
Security Rules
I implemented Firebase security rules to protect the data:
// Example Firebase security rules
{
"rules": {
"users": {
"$uid": {
// Users can read any user profile
".read": "auth !== null",
// Users can only write to their own profile
".write": "auth !== null && auth.uid === $uid"
}
},
"chats": {
"$chatId": {
// Users can read chats they are part of
".read": "auth !== null && data.child('participants').hasChild(auth.uid)",
// Chat creation and updates
".write": "auth !== null && (
// New chat
!data.exists() ||
// Existing chat participant
data.child('participants').hasChild(auth.uid) ||
// Group chat admin
(data.child('type').val() === 'group' &&
data.child('members').child(auth.uid).child('role').val() === 'admin')
)"
}
},
"messages": {
"$chatId": {
// Users can read messages from chats they are part of
".read": "auth !== null &&
root.child('chats').child($chatId).child('participants').hasChild(auth.uid)",
// Users can write messages to chats they are part of
".write": "auth !== null &&
root.child('chats').child($chatId).child('participants').hasChild(auth.uid)"
}
}
}
}
Rate Limiting
To prevent abuse, I implemented rate limiting for certain operations:
// Example rate limiting for message sending
const sendMessage = async (content, replyToId = null) => {
// Check if user has sent too many messages recently
const recentMessagesRef = query(
ref(db, `messages/${currentChat.id}`),
orderByChild('sender'),
equalTo(user.uid),
limitToLast(10)
);
const snapshot = await get(recentMessagesRef);
if (snapshot.exists()) {
const messages = Object.values(snapshot.val());
const now = Date.now();
const recentCount = messages.filter(msg =>
now - msg.timestamp < 10000 // Last 10 seconds
).length;
if (recentCount >= 5) {
throw new Error('You are sending messages too quickly. Please wait a moment.');
}
}
// Continue with sending message
// ...
};
Future API Enhancements
I have several ideas for enhancing the API in the future:
- WebSocket Integration: Move from Firebase Realtime Database to a custom WebSocket solution for more control
- GraphQL API: Implement a GraphQL layer for more efficient data fetching
- Caching Layer: Add a Redis caching layer for frequently accessed data
- Microservices: Split functionality into microservices for better scalability
- Analytics API: Add endpoints for collecting usage statistics and user engagement metrics