├── .gitignore ├── config ├── appConfig.js └── dbconfig.json ├── public ├── images │ ├── 1.jpg │ ├── 2.jpg │ └── 3.jpg ├── login.ejs └── chat.ejs ├── debug.log ├── migrations ├── 20170203112134-users-migration.js └── 20170204111728-messages-migration.js ├── routes ├── index.js ├── authenticationRoutes.js └── chatRoutes.js ├── package.json ├── models ├── index.js ├── messages.js └── users.js ├── seeders └── 20170825054100-createNewUsersToLoginWith.js ├── README.md └── server.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .idea/* -------------------------------------------------------------------------------- /config/appConfig.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'secret': 'iamthebest', 3 | }; -------------------------------------------------------------------------------- /public/images/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amirhamdy/real-time-chat/HEAD/public/images/1.jpg -------------------------------------------------------------------------------- /public/images/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amirhamdy/real-time-chat/HEAD/public/images/2.jpg -------------------------------------------------------------------------------- /public/images/3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amirhamdy/real-time-chat/HEAD/public/images/3.jpg -------------------------------------------------------------------------------- /debug.log: -------------------------------------------------------------------------------- 1 | [0724/221757.280:ERROR:crash_report_database_win.cc(428)] unexpected header 2 | [0724/222830.241:ERROR:crash_report_database_win.cc(428)] unexpected header 3 | [0724/223359.930:ERROR:crash_report_database_win.cc(428)] unexpected header 4 | -------------------------------------------------------------------------------- /migrations/20170203112134-users-migration.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var models = require("../models/index.js"); 3 | module.exports = { 4 | up: function (queryInterface, Sequelize) { 5 | 6 | return queryInterface.createTable(models.users.tableName, 7 | models.users.attributes); 8 | }, 9 | 10 | down: function (queryInterface, Sequelize) { 11 | return queryInterface.dropTable('users'); 12 | } 13 | }; 14 | 15 | -------------------------------------------------------------------------------- /migrations/20170204111728-messages-migration.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var models = require("../models/index.js"); 3 | module.exports = { 4 | up: function (queryInterface, Sequelize) { 5 | return queryInterface.createTable(models.messages.tableName, 6 | models.messages.attributes); 7 | }, 8 | 9 | down: function (queryInterface, Sequelize) { 10 | return queryInterface.dropTable('messages'); 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /config/dbconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "development": { 3 | "username": "root", 4 | "password": null, 5 | "database": "chat", 6 | "host": "127.0.0.1", 7 | "dialect": "mysql" 8 | }, 9 | "test": { 10 | "username": "root", 11 | "password": null, 12 | "database": "chat", 13 | "host": "127.0.0.1", 14 | "dialect": "mysql" 15 | }, 16 | "production": { 17 | "username": "root", 18 | "password": null, 19 | "database": "chat", 20 | "host": "127.0.0.1", 21 | "dialect": "mysql" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /routes/index.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | var authenticationRouter =require("./authenticationRoutes"); 3 | var chatRouter = require('./chatRoutes'); 4 | // import cors from "cors"; 5 | 6 | module.exports= function ConfigApiRoutes(app) { 7 | // app.use(cors()); 8 | app.use('/api',authenticationRouter); 9 | app.use('/api',chatRouter); 10 | 11 | app.get('/', (req, res) => { 12 | res.render('login'); 13 | }); 14 | 15 | app.get('/chat', (req, res) => { 16 | res.render('chat'); 17 | }); 18 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chat", 3 | "version": "1.0.0", 4 | "description": "Real Time chat using sockets.io", 5 | "main": "server.js", 6 | "scripts": { 7 | "start": "node server.js" 8 | }, 9 | "author": "amgad atef mohamed", 10 | "license": "ISC", 11 | "dependencies": { 12 | "body-parser": "^1.17.2", 13 | "ejs": "^2.5.7", 14 | "express": "^4.15.4", 15 | "jsonwebtoken": "^7.4.3", 16 | "md5": "^2.2.1", 17 | "mysql2": "^1.4.1", 18 | "open": "0.0.5", 19 | "sequelize": "^4.7.5", 20 | "socket.io": "^2.0.3" 21 | }, 22 | "devDependencies": { 23 | "nodemon": "^1.11.0" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /models/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var fs = require('fs'); 4 | var path = require('path'); 5 | var Sequelize = require('sequelize'); 6 | var basename = path.basename(module.filename); 7 | var env = process.env.NODE_ENV || 'development'; 8 | var config = require(__dirname + '/../config/dbconfig.json')[env]; 9 | var db = {}; 10 | 11 | if (config.use_env_variable) { 12 | var sequelize = new Sequelize(process.env[config.use_env_variable]); 13 | } else { 14 | var sequelize = new Sequelize(config.database, config.username, config.password, config); 15 | } 16 | 17 | fs 18 | .readdirSync(__dirname) 19 | .filter(function(file) { 20 | return (file.indexOf('.') !== 0) && (file !== basename) && (file.slice(-3) === '.js'); 21 | }) 22 | .forEach(function(file) { 23 | var model = sequelize['import'](path.join(__dirname, file)); 24 | db[model.name] = model; 25 | }); 26 | 27 | Object.keys(db).forEach(function(modelName) { 28 | if (db[modelName].associate) { 29 | db[modelName].associate(db); 30 | } 31 | }); 32 | 33 | db.sequelize = sequelize; 34 | db.Sequelize = Sequelize; 35 | 36 | module.exports = db; 37 | -------------------------------------------------------------------------------- /routes/authenticationRoutes.js: -------------------------------------------------------------------------------- 1 | "user strict"; 2 | var express = require('express'); 3 | var authenticationRouter = express.Router(); 4 | var jwt = require('jsonwebtoken'); 5 | var appSeckertKey = require('../config/appConfig').secret; 6 | var models = require('../models/index'); 7 | var md5 = require('md5'); 8 | 9 | authenticationRouter.post('/login', function (req, res) { 10 | try { 11 | models.users.findOne({where:{email: req.body.email, password: md5(req.body.password) }}).then( user => { 12 | if (!user) 13 | return res.status(401).json({ success: false, message: 'Authentication failed. User not found.' }); 14 | 15 | jwt.sign({'user_id': user.id,'first_name':user.first_name,'email': user.email,'avatar_path': user.avatar_path}, 16 | appSeckertKey, {expiresIn: 60 * 60* 60*24}, (err, token) =>{ 17 | if (err) 18 | console.log(err); 19 | return res.status(200).json({ success: true,'userToken': token }); 20 | }); 21 | }).catch(error => res.status(400).json(error)); 22 | } 23 | catch (err) { 24 | return res.status(500).json({success: false, message: "There was an error attempting to login. Please try again later."}); 25 | } 26 | }); 27 | 28 | module.exports = authenticationRouter; -------------------------------------------------------------------------------- /seeders/20170825054100-createNewUsersToLoginWith.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var md5 = require('md5'); 3 | module.exports = { 4 | up: function (queryInterface, Sequelize) { 5 | 6 | return queryInterface.bulkInsert('users', [{ 7 | first_name: 'Amir Hamdy', 8 | email: 'amirhamdy4@gmail.com', 9 | password: md5('123456'), 10 | remember_token: 'remember_token', 11 | avatarPath: '1.jpg', 12 | isActive: 0, 13 | socketID: null 14 | },{ 15 | first_name: 'Ahmed Hamdy', 16 | email: 'ahmedhamdy4@gmail.com', 17 | password: md5('123456'), 18 | remember_token: 'remember_token', 19 | avatarPath: '2.jpg', 20 | isActive: 0, 21 | socketID: null 22 | },{ 23 | first_name: 'Abdo Hamdy', 24 | email: 'abdohamdy4@gmail.com', 25 | password: md5('123456'), 26 | remember_token: 'remember_token', 27 | avatarPath: '3.jpg', 28 | isActive: 0, 29 | socketID: null 30 | }], {}); 31 | }, 32 | 33 | down: function (queryInterface, Sequelize) { 34 | /* 35 | Add reverting commands here. 36 | Return a promise to correctly handle asynchronicity. 37 | 38 | Example: 39 | return queryInterface.bulkDelete('Person', null, {}); 40 | */ 41 | } 42 | }; 43 | -------------------------------------------------------------------------------- /models/messages.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = function(sequelize, DataTypes) { 4 | var messages = sequelize.define("messages", { 5 | id: { 6 | type: DataTypes.INTEGER.UNSIGNED , 7 | primaryKey: true , 8 | autoIncrement: true 9 | }, 10 | message_subject : { 11 | type:DataTypes.STRING 12 | }, 13 | message_body :{ 14 | type: DataTypes.TEXT, 15 | allowNull:false 16 | } , 17 | sender_id : { 18 | type: DataTypes.INTEGER.UNSIGNED , 19 | allowNull: false, 20 | references: { 21 | model: "users", 22 | key: "id" 23 | } 24 | }, 25 | receiver_id :{ 26 | type: DataTypes.INTEGER.UNSIGNED , 27 | allowNull: false, 28 | references: { 29 | model: "users", 30 | key: "id" 31 | } 32 | }, 33 | conversation_id : { 34 | type:DataTypes.STRING, 35 | allowNull : false 36 | } , 37 | created_at : { 38 | type: 'TIMESTAMP' , 39 | allowNull: false, 40 | defaultValue: DataTypes.NOW 41 | }, 42 | updated_at :{ 43 | type: 'TIMESTAMP' , 44 | allowNull: false, 45 | defaultValue: DataTypes.NOW 46 | }, 47 | viewed : DataTypes.BOOLEAN 48 | }, 49 | { 50 | timestamps: false, 51 | freezeTableName:true, 52 | tableName: 'messages' 53 | }, { 54 | classMethods: { 55 | associate: function(models) { 56 | } 57 | } 58 | }); 59 | messages.associate = function(models) { 60 | messages.belongsTo(models.users, { 61 | onDelete: "CASCADE", 62 | foreignKey: 'sender_id', 63 | targetKey: 'id', 64 | as :'user' 65 | }); 66 | }; 67 | return messages; 68 | }; 69 | -------------------------------------------------------------------------------- /models/users.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var md5 = require('md5'); 3 | module.exports = function(sequelize, DataTypes) { 4 | var users = sequelize.define("users", { 5 | id: { 6 | type: DataTypes.INTEGER.UNSIGNED , 7 | primaryKey: true , 8 | autoIncrement: true 9 | }, 10 | first_name : { 11 | type:DataTypes.STRING, 12 | allowNull: false, 13 | }, 14 | email : { 15 | type:DataTypes.STRING, 16 | allowNull: false, 17 | unique:true 18 | } , 19 | password : { 20 | type:DataTypes.STRING, 21 | allowNull: false, 22 | set(val) { 23 | this.setDataValue('password', md5(val)); 24 | } 25 | }, 26 | remember_token :{ 27 | type:DataTypes.STRING, 28 | allowNull: false, 29 | }, 30 | created_at :{ 31 | type: 'TIMESTAMP' , 32 | allowNull: false, 33 | defaultValue: DataTypes.NOW 34 | }, 35 | updated_at :{ 36 | type: 'TIMESTAMP' , 37 | allowNull: false, 38 | defaultValue: DataTypes.NOW 39 | }, 40 | avatarPath : DataTypes.TEXT , 41 | isActive: DataTypes.BOOLEAN, 42 | socketID: { 43 | type:DataTypes.STRING(255), 44 | defaultValue:null, 45 | allowNull:true 46 | } 47 | 48 | } 49 | , 50 | { 51 | timestamps: false, 52 | freezeTableName:true, 53 | tableName: 'users' 54 | } 55 | , { 56 | classMethods: { 57 | associate: function(models) { 58 | } 59 | } 60 | }); 61 | users.associate = function(models) { 62 | users.hasMany(models.messages, { 63 | onDelete: "CASCADE", 64 | foreignKey:'sender_id', 65 | targetKey: 'id', 66 | as: 'userMessages' 67 | }); 68 | }; 69 | return users; 70 | }; 71 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | A Real Time Chat Application built using Node.js, Express, MYSQL , Socket.io, JWT 2 | # Features 3 | * Uses Express as the application Framework. 4 | * Manages authentication using [express jwt package](https://www.npmjs.com/package/jwt-express). 5 | * Passwords are hashed using [md5 package](https://www.npmjs.com/package/md5) . 6 | * Real-time communication between a client and a server using [Socket.io](https://socket.io/). 7 | * Uses [Sequelize ORM](https://github.com/sequelize/sequelize) for storing and querying data to MYSQL. 8 | * - [x] After User Logging to the system , he can see all users in the system. 9 | * - [x] Can define each user is online or offline. 10 | * - [x] User can messaging with any user in the system , see all previous messages (private messages). 11 | * - [x] When user send a new message to another user , he get notification that he has a new message. 12 | 13 | # incomming Features: 14 | * - [ ] When user send message to another one and that one is offline , get get notification 15 | after login agian that he has new message from sender user. 16 | 17 | 18 | > # Installation steps: 19 | 20 | * make sure you have mysql , node js and npm installed in your operation system . 21 | * Create new database named chat . 22 | * Clone or Download the repository 23 | ``` 24 | git clone https://github.com/amirhamdy4b/real-time-chat.git 25 | ``` 26 | * Install Dependencies 27 | ``` 28 | npm install 29 | npm install sequelize-cli -g 30 | ``` 31 | * Edit configuration file in config/dbconfig.json with your credentials. 32 | * run migrations to create tables in database. 33 | ``` 34 | * sequelize db:migrate --config config/dbconfig.json 35 | ``` 36 | * run seeds to insert 5 new user in database to be used in the system. 37 | ``` 38 | * sequelize db:seed:all --config config/dbconfig.json 39 | ``` 40 | * Start the application 41 | ``` 42 | * npm start 43 | ``` 44 | Your app should now be running on localhost:3000. 45 | -------------------------------------------------------------------------------- /public/login.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |