![todo: get a logo](/NanddoSalas/ChitChatApp/raw/main/assets/logo.png)
Full Stack room-based Chat Application
Try Demo
Table of Contents
ChitChatApp is a room-based chat application. This Full Stack project enables users to create, join, and engage in chat rooms seamlessly. With a focus on real-time communication ChitChatApp is built with a modern tech stack, combining powerful backend technologies with sleek front-end frameworks to deliver a smooth and engaging user experience.
- Room Creation and Management: Easily create and manage multiple chat rooms.
- User Authentication: Secure user login and registration using modern authentication techniques.
- Responsive Design: Accessible on both desktop and mobile devices for a seamless experience.
- Real-Time Messaging: Enjoy instant messaging powered by WebSockets for real-time communication.
The primary motivation behind creating ChitChatApp was to enhance my skills in both backend and frontend development. This project serves as a hands-on platform to achieve the following goals:
-
Learn a New Backend Technology: One of the key objectives was to gain proficiency in Java and Spring. By integrating these technologies, I aimed to build a robust and scalable backend system that supports real-time communication and efficient data management.
-
Enhance Frontend Development Skills: Another important goal was to deepen my knowledge as a frontend developer. This project provided an opportunity to work with modern frontend frameworks and tools, focusing on creating a seamless and responsive user interface that enhances the user experience.
The API is secured by JWT Authentication, where a user can choose to sign in using credentials (email and password) or a Google Account to get an Access Token and use it for subsequent requests:
sequenceDiagram
participant R as React App
participant S as Spring Server
participant G as Google
R -->> + S: GET /rooms
S -->> - R: HTTP 401 Unauthorized
R -->> + S: <br/><br/><br/>POST /auth/signin, credentials
S -->> - R: HTTP 200 OK, accessToken
R -->> + G: <br/><br/><br/>Authenticate with Google and Consent
G -->> - R: Authorization Code
R -->> + S: POST /auth/signin/google, authorization code
S -->> + G: Exchange authorization code
G -->> - S: ID Token
S -->> - R: HTTP 200 OK, accessToken
R -->> + S: <br/><br/><br/>GET /roms, accessToken
S -->> - R: HTTP 200 OK, body
Every authenticated user will have a role assigned to it, it could be:
- Member: A member can send, see, and receive messages from other users, public rooms and private rooms where he is a member of.
- Admin: An admin can do all a member can do, and also manage invitation and rooms (can't manage invitations or rooms created by other users).
- ServerAdmin: A server admin can do all an admin can do, and also he's able to update user's role (from member to admin, back and forth).
POST
/auth/signup
(Sign up with credentials) (Role required: none)
Name | Type | Required |
string | true | |
password | string | true |
fullName | string | true |
inviteCode | string | true |
Status | Response Body Example |
200 |
{
"data": null,
"errors": null
} |
404 |
{
"data": null,
"errors": {
"inviteCode": "Invalid invitation code",
"email": "Email already in use"
}
} |
POST
/auth/signup/google
(Sign up with Google) (Role required: none)
Name | Type | Required |
code | string | true |
inviteCode | string | true |
Status | Response Body Example |
200 |
{
"data": null,
"errors": null
} |
404 |
{
"data": null,
"errors": {
"message": "Unable to Sign up with that Google Account, try a different one!"
}
} |
POST
/auth/signin
(Sign in with credential) (Role required: none)
Name | Type | Required |
string | true | |
password | string | true |
Status | Response Body Example |
200 |
{
"data": {
"accessToken": "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxIiwiZW1haWwiOiJhbGljZS5qb2huc29uQGV4YW1wbGUuY29tIiwic2NvcGUiOiJTZXJ2ZXJBZG1pbiJ9.kC3rk6NQSbDCEJTciLDSPjZF7JO7eraa6imLn_yO3PA",
"user": {
"id": 1,
"fullName": "Alice Johnson",
"email": "[email protected]",
"avatar": "https://randomuser.me/api/portraits/women/1.jpg",
"about": "Loves hiking and outdoor adventures.",
"role": "ServerAdmin",
"creationDate": "2024-07-12 20:12:44.825401",
"hasPassword": true,
"hasGoogle": false,
"hasGigHub": false
}
},
"errors": null
} |
404 |
{
"data": null,
"errors": {
"message": "Invalid credentials"
}
} |
POST
/auth/signin/google
(Sign in with Google) (Role requires: none)
Name | Type | Required |
code | string | true |
Status | Response Body Example |
200 |
{
"data": {
"accessToken": "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxIiwiZW1haWwiOiJhbGljZS5qb2huc29uQGV4YW1wbGUuY29tIiwic2NvcGUiOiJTZXJ2ZXJBZG1pbiJ9.kC3rk6NQSbDCEJTciLDSPjZF7JO7eraa6imLn_yO3PA",
"user": {
"id": 1,
"fullName": "Alice Johnson",
"email": "[email protected]",
"avatar": "https://randomuser.me/api/portraits/women/1.jpg",
"about": "Loves hiking and outdoor adventures.",
"role": "ServerAdmin",
"creationDate": "2024-07-12 20:12:44.825401",
"hasPassword": true,
"hasGoogle": false,
"hasGigHub": false
}
},
"errors": null
} |
404 |
{
"data": null,
"errors": {
"message": "User does not exist, Sign up first!"
}
} |
GET
/users
(Retrieve all Users) (Role requires: Member)
Status | Response Body Example |
200 |
{
"data": [
{
"id": 1,
"fullName": "Alice Johnson",
"email": "[email protected]",
"avatar": "https://randomuser.me/api/portraits/women/1.jpg",
"about": "Loves hiking and outdoor adventures.",
"role": "ServerAdmin",
"creationDate": "2024-07-12 20:12:44.825401"
},
{
"id": 2,
"fullName": "Bob Smith",
"email": "[email protected]",
"avatar": "https://randomuser.me/api/portraits/men/1.jpg",
"about": "Avid reader and writer.",
"role": "Member",
"creationDate": "2024-07-12 20:12:44.825401"
}
],
"errors": null
} |
PUT
/users/${userId}/role
(Update User's Role) (Role requires: ServerAdmin)
Name | Type | Required |
role | "Member" | "Admin" | true |
Status | Response Body Example |
200 |
{
"data": null,
"errors": null
} |
PUT
/users/${userId}/profile
(Update User's Profile) (Role requires: Member)
Name | Type | Required |
fullName | string | true |
about | string | true |
Status | Response Body Example |
200 |
{
"data": null,
"errors": null
} |
PUT
/users/${userId}/password
(Update User's Password) (Role requires: Member)
Name | Type | Required |
oldPassword | string | true |
newPassword | string | true |
Status | Response Body Example |
200 |
{
"data": null,
"errors": null
} |
400 |
{
"data": null,
"errors": {
"oldPassword": "Invalid old password"
}
} |
PUT
/users/${userId}/google
(Connect Google Account) (Role requires: Member)
Name | Type | Required |
code | string | true |
Status | Response Body Example |
200 |
{
"data": null,
"errors": null
} |
400 |
{
"data": null,
"errors": {
"message": "Unable to connect that Google Account, try a different one!"
}
} |
DELETE
/users/${userId}/google
(Disconnect Google Account) (Role requires: Member)
Name | Type | Required |
Status | Response Body Example |
200 |
{
"data": null,
"errors": null
} |
GET
/invitations
(Retrieve all Invitations) (Role requires: Admin)
Status | Response Body Example |
200 |
{
"data": [
{
"id": 1,
"inviteCode": "YAgSKjtGGVseZgva",
"uses": 2,
"maxUses": 10,
"revoked": false,
"creatioDate": "2024-07-12 20:12:44.837065"
},
{
"id": 4,
"inviteCode": "YAgSKjtGGVseZgva",
"uses": 1,
"maxUses": 3,
"revoked": false,
"creatioDate": "2024-07-12 20:13:52.06593"
}
],
"errors": null
} |
POST
/invitations
(Create Invitation) (Role requires: Admin)
Name | Type | Required |
limit | number | false |
Status | Response Body Example |
200 |
{
"data": {
"id": 5,
"inviteCode": "BU5DOkGngG57EVSb",
"uses": 0,
"maxUses": 3,
"revoked": false,
"creatioDate": "2024-07-12 20:45:01.546282"
},
"errors": null
} |
DELETE
/invitations/${invitationId}
(Revoke Invitation) (Role requires: Admin)
Status | Response Body Example |
200 |
{
"data": null,
"errors": null
} |
GET
/rooms
(Retrieve all Rooms) (Role requires: Member)
Status | Response Body Example |
200 |
{
"data": [
{
"id": 1,
"roomName": "General Chat",
"creatorId": 1,
"creationDate": "2024-07-12 20:47:21.250011",
"hasAccess": true,
"private": false
},
{
"id": 2,
"roomName": "Gaming Room",
"creatorId": 3,
"creationDate": "2024-07-12 20:47:21.250011",
"hasAccess": false,
"private": false
},
{
"id": 3,
"roomName": "Admin Room",
"creatorId": 1,
"creationDate": "2024-07-12 20:47:21.250011",
"hasAccess": true,
"private": true
}
],
"errors": null
} |
POST
/rooms
(Create Room) (Role requires: Admin)
Name | Type | Required |
name | string | true |
private | boolean | false |
Status | Response Body Example |
200 |
{
"data": {
"id": 6,
"roomName": "Awesome private room",
"creatorId": 1,
"creationDate": "2024-07-12 20:52:20.460003",
"hasAccess": true,
"private": true
},
"errors": null
} |
PUT
/rooms/${roomId}
(Update Room) (Role requires: Admin)
Name | Type | Required |
name | string | true |
private | boolean | true |
Status | Response Body Example |
200 |
{
"data": null,
"errors": null
} |
DELETE
/rooms/${roomId}
(Delete Room) (Role requires: Admin)
Status | Response Body Example |
200 |
{
"data": null,
"errors": null
} |
GET
/rooms/${roomId}/members
(Retrieve all Room Members) (Role requires: Member)
Status | Response Body Example |
200 |
{
"data": [
{
"userId": 1,
"memberSince": "2024-07-12 20:47:21.255017"
},
{
"userId": 2,
"memberSince": "2024-07-12 20:47:21.255017"
},
{
"userId": 3,
"memberSince": "2024-07-12 20:47:21.255017"
},
{
"userId": 4,
"memberSince": "2024-07-12 20:47:21.255017"
}
],
"errors": null
} |
POST
/rooms/${roomId}/members
(Create Room Member) (Role requires: Admin)
Name | Type | Required |
userId | number | true |
Status | Response Body Example |
200 |
{
"data": {
"roomId": 1,
"userId": 2,
"creationDate": "2024-07-12 21:01:00.173227"
},
"errors": null
} |
DELETE
/rooms/${roomId}/members/${userId}
(Delete Room Member) (Role requires: Admin)
Status | Response Body Example |
200 |
{
"data": null,
"errors": null
} |
GET
/users/${userId}/messages
(Retrieve paginated Direct Messages) (Role requires: Member)
Name | Type | Required |
cursor | number | false |
Status | Response Body Example |
200 |
{
"data": [
{
"id": 2,
"senderId": 2,
"creationDate": "2024-07-12 20:47:21.266432",
"body": "Sure Alice, I will check it now."
},
{
"id": 1,
"senderId": 1,
"creationDate": "2024-07-12 20:47:21.266432",
"body": "Hey Bob, can you check the admin panel?"
}
],
"errors": null
} |
GET
/rooms/${roomId}/messages
(Retrieve paginated Room Messages) (Role requires: Member)
Name | Type | Required |
cursor | number | false |
Status | Response Body Example |
200 |
{
"data": [
{
"id": 9,
"senderId": 1,
"creationDate": "2024-07-12 20:47:21.260368",
"body": "Here is a song I like."
},
{
"id": 8,
"senderId": 5,
"creationDate": "2024-07-12 20:47:21.260368",
"body": "Let’s share some music recommendations."
}
],
"errors": null
} |
POST
/users/${userId}/messages
(Send Direct Message) (Role requires: Member)
Name | Type | Required |
body | string | true |
Status | Response Body Example |
200 |
{
"data": {
"id": 12,
"senderId": 1,
"creationDate": "2024-07-12 21:09:25.748969",
"body": "Hi there"
},
"errors": null
} |
POST
/rooms/${roomId}/messages
(Send Room Message) (Role requires: Member)
Name | Type | Required |
body | string | true |
Status | Response Body Example |
200 |
{
"data": {
"id": 7,
"senderId": 1,
"creationDate": "2024-07-12 21:09:48.029569",
"body": "Hi there"
},
"errors": null
} |
Real-Time functionality is built using STOMP (Streaming Text Oriented Messaging Protocol) over WebSockets, where a user hits an API endpoint that creates, updates or deletes resources and those changes will be sended to subscribed clients.
Endpoints where a client can suscribe to:
Public STOMP Destination |
---|
/topic/new-user |
/topic/update-user |
/topic/new-room |
/topic/update-room |
/topic/delete-room |
/topic/new-room-message |
Filtered STOMP Destinations | Who recieves |
---|---|
/user/queue/update-role | User who's role been updated |
/user/queue/new-private-room-message | Members of the private room |
/user/queue/new-direct-message | User whose message is sent to |
/user/queue/new-room-member | User who became a room's member |
/user/queue/delete-room-member | User whose no longer a room's member |
This project is made up of 2 packages.
server/
(Spring server)packages/web/
(React app)
- Java 17
- Node.js v17
- PostgreSQL database
- Google Client ID and Secret for Google Authentication
- Get the code into your local machine
git clone https://github.com/NanddoSalas/ChitChatApp.git
cd ChitChatApp
- Install React app dependencies
yarn install
- Setup React app environment at
packages/web/.env
cp packages/web/.env.example packages/web/.env
Sample environment
VITE_GOOGLE_CLIENT_ID=
VITE_API_URL=http://localhost:8080
VITE_STOMP_URL=ws://localhost:8080/socket
- Start React app
yarn dev
- Setup Spring server application properties at
server/src/main/resources/application.properties
Sample application properties
spring.application.name=ChitChatApp Server
spring.datasource.url=jdbc:postgresql://localhost:5432/chitchatapp
spring.datasource.username=user
spring.datasource.password=password
spring.sql.init.mode=always
secret.key=defautlSecret
google.clientId=
google.clientSecret=
google.redirectUri=http://localhost:5173
- Start Spring server
cd server
./mvnw spring-boot:run
Distributed under the MIT License. See LICENSE for more information.
````