In this blog, we’ll walk through the process of integrating WebSocket functionality using Socket.IO, adding caching support with Redis, and hosting a full-stack application built with Flask and Next.js using Docker Compose. By the end, you’ll have a scalable, real-time web application architecture ready to deploy.
Tech Stack Overview
- Backend: Flask with extensions like Flask-SocketIO for real-time communication, Flask-Session for session management, and SQLAlchemy for database interactions.
- Frontend: Next.js with client-side Socket.IO for real-time content synchronization.
- Database: PostgreSQL for persistent data storage.
- Cache: Redis for caching and quick data access.
- Hosting: Docker Compose for containerized deployment.
Step 1: Backend Setup with Flask and Redis
Flask Backend Structure
Here’s the structure of our Flask backend:
- Initialize Flask and Extensions:
from flask import Flask from flask_sqlalchemy import SQLAlchemy from flask_socketio import SocketIO from flask_session import Session import redis, os app = Flask(__name__) app.config["SECRET_KEY"] = "your_secret_key" app.config["SQLALCHEMY_DATABASE_URI"] = "postgresql://username:password@postgres:5432/dbname" app.config["SESSION_TYPE"] = "redis" app.config["SESSION_PERMANENT"] = False app.config["SESSION_USE_SIGNER"] = True app.config["SESSION_KEY_PREFIX"] = "session:" app.config["SESSION_REDIS"] = redis.StrictRedis(host=os.getenv('REDIS_HOST', 'redis'), port=6379, db=0) # Initialize extensions socketio = SocketIO(app, cors_allowed_origins="*") db = SQLAlchemy(app) Session(app)
- Add Real-Time WebSocket Functionality:
from flask_socketio import emit, join_room, leave_room @socketio.on('join_room') def handle_join_room(data): room = data.get('contentId') join_room(room) emit('user_joined', {'msg': 'A new user has joined the room'}, to=room) @socketio.on('edit_content') def handle_edit_content(data): content_id = data.get('contentId') content = data.get('content') emit('content_update', {'content': content}, to=content_id, skip_sid=request.sid) @socketio.on('leave_room') def handle_leave_room(data): room = data.get('contentId') leave_room(room) emit('user_left', {'msg': 'A user has left the room'}, to=room)
- Session and Redis Configuration:
import redis cache = redis.StrictRedis(host=os.getenv('REDIS_HOST', 'redis'), port=6379, db=0)
- Environment Configuration (.env file):
SECRET_KEY=your_secret_key SQLALCHEMY_DATABASE_URI=postgresql://postgres:postgres@postgres:5432/postgres SQLALCHEMY_TRACK_MODIFICATIONS=False APP_URL=AWS EC2 IP address HOST=redis
Step 2: Frontend Setup with Next.js and Socket.IO
Client-Side Socket.IO Setup
- Install Socket.IO Client:
npm install socket.io-client
- Create a Socket Instance:
// socket.js import { io } from "socket.io-client"; const SOCKET_URL = process.env.NEXT_PUBLIC_SOCKET_URL; const socket = io(SOCKET_URL, { autoConnect: false, }); export default socket;
- Integrate TipTap Editor with Real-Time Updates:
"use client"; import socket from "@/shared/socket"; import { useEditor, EditorContent } from "@tiptap/react"; import StarterKit from "@tiptap/starter-kit"; import { useEffect, useState } from "react"; const Tiptap = ({ content = "", contentId }) => { const [isSaving, setIsSaving] = useState(false); const editor = useEditor({ extensions: [StarterKit], content, onUpdate: ({ editor }) => { const updatedContent = editor.getHTML(); if (contentId) { setIsSaving(true); socket.emit("edit_content", { contentId, content: updatedContent }); setTimeout(() => setIsSaving(false), 1000); } }, }); useEffect(() => { if (contentId) { if (!socket.connected) socket.connect(); socket.emit("join_room", { contentId }); socket.on("content_update", (data) => { if (editor && data.content !== editor.getHTML()) { editor.commands.setContent(data.content); } }); return () => { socket.emit("leave_room", { contentId }); socket.off("content_update"); }; } }, [contentId, editor]); return <EditorContent editor={editor} />; }; export default Tiptap;
- Environment Configuration (.env file):
NEXT_PUBLIC_API_URL=http://localhost:5000/api NEXT_PUBLIC_SOCKET_URL=http://localhost:5000 DATABASE_NAME=postgres DATABASE_USER=postgres DATABASE_PASSWORD=postgres
Step 3: Docker Compose for Deployment
-
\
** Configuration**:
version: "3.8" services: nginx: image: nginx:alpine container_name: nginx_proxy ports: - "443:443" - "80:80" volumes: - ./nginx/nginx.conf:/etc/nginx/nginx.conf depends_on: - backend - website networks: - app-network website: build: context: ./website dockerfile: Dockerfile args: NEXT_PUBLIC_SOCKET_URL: ${NEXT_PUBLIC_SOCKET_URL} container_name: website environment: - NEXT_PUBLIC_SOCKET_URL=${NEXT_PUBLIC_SOCKET_URL} expose: - "3000" networks: - app-network backend: build: context: ./backend dockerfile: Dockerfile container_name: backend expose: - "5000" networks: - app-network redis: image: redis:alpine container_name: redis expose: - "6379" networks: - app-network postgres: image: postgres:14-alpine container_name: postgres environment: - POSTGRES_DB=${DATABASE_NAME} - POSTGRES_USER=${DATABASE_USER} - POSTGRES_PASSWORD=${DATABASE_PASSWORD} volumes: - postgres_data:/var/lib/postgresql/data networks: - app-network networks: app-network: driver: bridge volumes: postgres_data:
- Nginx Configuration for Proxy:
server { listen 80; server_name _; location / { proxy_pass http://website:3000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } location /api/ { proxy_pass http://backend:5000; proxy_set_header Host $host; } location /socket.io/ { proxy_pass http://backend:5000/socket.io/; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; } }
Step 4: Running the Application
- Build and start the services:
docker-compose up --build
- Access the application:
- Frontend:
http://localhost
- Backend API:
http://localhost/api
- Frontend:
Top comments (0)