Xây dựng app chat realtime với VueJS – NodeJS – Express – SocketIO

Mở đầu Từ lâu mình đã ấp ủ tự mình làm được một app chat để chat với bạn bè. Do dạo này nghỉ dịch, mình có nhiều thời gian để học thêm công nghệ mới và trong đó có Vuejs. Học thì phải đi đôi với hành nên mình vừa thực hành Vuejs tiện

Mở đầu

Từ lâu mình đã ấp ủ tự mình làm được một app chat để chat với bạn bè. Do dạo này nghỉ dịch, mình có nhiều thời gian để học thêm công nghệ mới và trong đó có Vuejs. Học thì phải đi đôi với hành nên mình vừa thực hành Vuejs tiện thể thực hiện dự định của mình luôn.

Mình sẽ viết lại quá trình mà mình thực hành với project App chat realtime với Vue - Nodejs - Express - SocketIO. Các bạn cùng học và thực hành với mình nhé.

Series này sẽ có các phần:

  1. Cài đặt môi trường phát triển VueJS, NodeJS, Express, SocketIO và xây dựng login.
  2. Phát triển tính năng chat private chat group.
  3. Hoàn chỉnh backend server, kết nối MongoDB, thêm các tính năng Authen và lưu trữ tin nhắn.
  4. Deploy sản phẩm lên Netlify và Heroku.

Và hôm nay chúng ta sẽ đến với phần đầu tiên của series này.


I. Cài đặt môi trường

Frontend

Với VueJS mình sử dùng Vue CLI để tạo project.

Trước hết, các bạn cần cài đặt Vue CLI bằng npm.

$ npm install --global vue-cli

Tạo project với Vue CLI.

$ vue create <tên-project>
  > Chọn Defalut ([Vue2] babel, eslint)

Cài đặt thư viện cần thiết.

Để việc phát triển app nhanh chóng, mình sẽ sử dụng thư việc component dành cho VueJS là Vuetify

$ vue add vuetify
  > Chọn Defalut
$ yarn add vuex vue-router axios vue-axios socket.io-client

Chạy project

$ vue serve

Cấu hình các thư viện

  1. Vuetify

Khi chạy vue add vuetify là vue đã tự động config.

  1. Vuex

store/store.js

import Vue from"vue";import Vuex from"vuex";

Vue.use(Vuex);const store =newVuex.Store({
  state:{},
  mutations:{},
  actions:{},
  getters:{},});exportdefault store;

main.js

...import store from"./store/store";

Vue.config.devtools =true;newVue({...
  store,...}).$mount("#app");
  1. Vue Router

routes/router.js

import Vue from"vue";import VueRouter from"vue-router";

Vue.use(VueRouter);const routes =[];const router =newVueRouter({
  routes,});exportdefault router;

main.js

...import router from"./routes/router";newVue({...
  router,...}).$mount("#app");
  1. Dotenv – Khởi tạo biến môi trường

.env và .env.default

VUE_APP_BACKEND_SERVER=http://localhost:4000

configs/constants.js

export constBACKEND_SERVER=
  process.env.VUE_APP_BACKEND_SERVER||"http://localhost:4000";

Backend

Với NodeJS – Express mình sẽ tạo project từ đầu.

Các bạn tạo thư mục mới với tên project, và mở thư mục đó lên với terminal và làm theo các bước sau:

Khởi tạo

$ yarn init -y

Cài đặt thư viện

$ yarn add express cors dotenv socket.io

$ yarn add --dev nodemon @babel/cli @babel/core @babel/node @babel/preset-env babel-plugin-module-resolver

Cấu trúc project

server
├── node_modules
├── src
|   ├── configs
||   └── constants.js
|   └── server.js
├── .babelrc.js
├── .env
├── .env.default
├── jsconfig.json
├── nodemon.json
├── package.json
└── yarn.lock

Thiết lập các công cụ phát triển

  1. Nodemon – Tự động reload server khi có thay đổi code

nodemon.json

{"watch":["src"],"ext":".js","ignore":[],"exec":"babel-node ./src/server.js"}
  1. Babel + jsconfig + absolute path

.babelrc.js

const path =require("path");const jsConfig =require("./jsconfig.json");

module.exports ={
  presets:["@babel/preset-env"],
  sourceMaps:true,
  plugins:[["module-resolver",{
        root:[path.resolve(jsConfig.compilerOptions.baseUrl)],},],],};

jsconfig.json

{"compilerOptions":{"module":"commonjs","target":"es6","baseUrl":"src"},"exclude":["node_modules"],"include":["src"]}
  1. Dotenv – Khởi tạo biến môi trường

.env và .env.default

PORT=4000

src/configs/constants.js

import dotenv from"dotenv";
dotenv.config();exportconstPORT= process.env.PORT||4000;

Thiết lập script trong package.json

"scripts":{"dev":"nodemon","start":"node build/server.js","build":"babel ./src -d ./build",}

Code file server

server.js

import express from"express";import cors from"cors";import{PORT}from"configs/constants";const app =express();

app.use(cors());
app.use(express.json());
app.use(express.urlencoded({ extended:false}));

app.get("/",(req, res)=>{
  res.json({ message:"Welcome"});});

app.listen(PORT,()=>{
  console.log("Server is running at PORT",PORT);});

Chạy server

$ yarn dev

Như vậy đã xong phần setup project, các bạn thử chạy cả backend lẫn frontend

$ yarn serve
$ yarn dev

Sau đó mở trình duyệt và vào địa chỉ http://localhost:8080http://localhost:4000 xem được chưa nhé.

Tiếp theo mình sẽ đến phần coding.

II. Cấu hình socket

Cấu hình socket phía client

socket/socket.js

import{ io }from"socket.io-client";import{BACKEND_SERVER}from"@/configs/constants";const socket =io(BACKEND_SERVER,{ autoConnect:false});functioncreateWebSocketPlugin(socket){return(store)=>{
    store.$socket = socket;// Thêm các socket event listening};}// Plugin lưu giá trị nhận được từ Socket vào Vuex storeexportconst websocketPlugin =createWebSocketPlugin(socket);exportdefault socket;

store/store.js

...import{ websocketPlugin }from"../socket/socket";const store =newVuex.Store({
  plugins:[websocketPlugin],...})

Cấu hình socket phía server

configs/socket.js

import socketIO from"socket.io";constcreateSocketIO=(httpServer)=>{const io =socketIO(httpServer,{
    allowEIO3:true,
    cors:{
      origin:true,
      credentials:true,},});

  io.on("connect",(socket)=>{});};exportdefault createSocketIO;

Các bạn sửa lại toàn bộ file server.js như sau:

server.js

import express from"express";import cors from"cors";import{ createServer }from"http";// otherimport{PORT}from"configs/constants";import createSocketIO from"configs/socket";// configconst app =express();const httpServer =createServer(app);createSocketIO(httpServer);// middlewares
app.use(cors());
app.use(express.json());
app.use(express.urlencoded({ extended:false}));

app.get("/",(req, res)=>{
  res.json({ message:"Welcome"});});

httpServer.listen(PORT,()=>{
  console.log("Server is running at PORT",PORT);});

III. Xây dựng tính năng Login

Với phần 1 của series này mình sẽ làm 2 màn hình: LoginHome. Khi vào app, màn hình Login sẽ hiện ra yêu cầu người dùng nhập Tên, sau khi nhập tên xong thì sẽ chuyển sang trang Home là trang chat chính.

Cụ thể workflow login như sau

Ảnh 1: Login workflow
  • Nhập tên người dùng, sau đó client connect với socket phía server và gửi thông thin username. Ở phía server khi connect sẽ emit event “USER_INFO”, gửi thông tin username và socketId về phía client

  • Phía client socket listening event “USER_INFO”, chờ khi socket gửi thông tin user xuống thì lưu vào Vuex store sau đó redirect sang trang Home.

Client

pages/Home.vue

<template><p>Home</p></template>

pages/Login.vue

<template><v-mainclass="main"><v-form@submit.prevent="login"><v-container><v-rowjustify="center"><v-colcols="12"sm="8"md="6"><v-text-fieldv-model="form.username"label="Username"placeholder="Username":rules="formRules.username"requiredoutlined></v-text-field></v-col></v-row><v-rowjustify="center"><v-btncolor="primary"type="submit"elevation="2"large>Go!</v-btn></v-row></v-container></v-form></v-main></template><script>import socket from"@/socket/socket";exportdefault{data(){return{
        formRules:{
          username:[(v)=>!!v ||"Username is required"],},
        form:{
          username:"",},};},
    methods:{login(){const{ username }=this.form;if(!username.length)return;
        socket.auth ={ username };
        socket.connect();},},
    computed:{user(){returnthis.$store.getters.user;},},
    watch:{// Khi có user tức là Login thành công => redirect sang trang Homeuser(newValue, oldValue){if(!oldValue && newValue){this.$router.push("/");}},},};</script><stylescoped>.main{background-image:url("https://www.wallpaperbetter.com/wallpaper/555/606/469/sea-sky-beach-2K-wallpaper.jpg");background-repeat: no-repeat;background-size: cover;background-position: center;}.container{margin-top: 200px;}</style>

App.vue

<template><v-app><router-view></router-view></v-app></template><style>html,
  body{height: 100vh;width: 100vw;overflow: hidden;}</style>

store/store.js

...const store =newVuex.Store({
  plugins:[websocketPlugin],
  state:{
    user:null,},
  mutations:{setUser(state, user){
      state.user = user;},},
  actions:{LOGIN({ commit }, user){commit("setUser", user);},},
  getters:{user(state){return state.user;},},});...

socket/socket.js

...functioncreateWebSocketPlugin(socket){return(store)=>{
    store.$socket = socket;// Khi socket server gửi thông tin user về thì lưu vào Vuex store
    socket.on("USER_INFO",(user)=> store.dispatch("LOGIN", user));};}...

routes/router.js

...import Home from"@/pages/Home";import Login from"@/pages/Login";import store from"@/store/store";...const routes =[{
    name:"Home",
    path:"/",
    component: Home,},{
    name:"Login",
    path:"/login",
    component: Login,},];...
router.beforeEach((to, from, next)=>{if(to.name !=="Login"&&!store.state.user)next({ name:"Login"});elseif(to.name ==="Login"&& store.state.user)next({ name:"Home"});elsenext();});...
Ảnh 2: Giao diện Login

Server

Khi user được kết nối, sẽ gửi lại thông tin user về phía client

configs/socket.js

const createSocketIO =(httpServer)=>{...
  io.on('connect',(socket)=>{// Userconst user ={
      socketId: socket.id,
      username: socket.handshake.auth.username,}// when connected, send user info to user
    socket.emit('USER_INFO', user)})}

Lời kết

OK có lẽ mình nên tạm dừng ở đây, ban đầu mình định làm chat private ở phần này nhưng bài đã quá dài rồi.

Các bạn có thể tham khảo source code của mình tại đây, mình đã làm xong phần chat private rồi nhé.

server: https://github.com/pika-3kw/chatme_server/tree/part1

client: https://github.com/pika-3kw/chatme_webapp/tree/part1

Rất mong các bạn ửng hộ để mình có động lực viết tiếp các phần sau.

Nguồn: viblo.asia

Bài viết liên quan

Thay đổi Package Name của Android Studio dể dàng với plugin APR

Nếu bạn đang gặp khó khăn hoặc bế tắc trong việc thay đổi package name trong And

Lỗi không Update Meta_Value Khi thay thế hình ảnh cũ bằng hình ảnh mới trong WordPress

Mã dưới đây hoạt động tốt có 1 lỗi không update được postmeta ” meta_key=

Bài 1 – React Native DevOps các khái niệm và các cài đặt căn bản

Hướng dẫn setup jenkins agent để bắt đầu build mobile bằng jenkins cho devloper an t

Chuyển đổi từ monolith sang microservices qua ví dụ

1. Why microservices? Microservices là kiến trúc hệ thống phần mềm hướng dịch vụ,