DEV Community

Cover image for Building a Real-Time Flask and Next.js Application with Redis, Socket.IO, and Docker Compose
Dhruv Kumar
Dhruv Kumar

Posted on

Building a Real-Time Flask and Next.js Application with Redis, Socket.IO, and Docker Compose

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:

  1. 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) 
Enter fullscreen mode Exit fullscreen mode
  1. 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) 
Enter fullscreen mode Exit fullscreen mode
  1. Session and Redis Configuration:
import redis cache = redis.StrictRedis(host=os.getenv('REDIS_HOST', 'redis'), port=6379, db=0) 
Enter fullscreen mode Exit fullscreen mode
  1. 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 
Enter fullscreen mode Exit fullscreen mode

Step 2: Frontend Setup with Next.js and Socket.IO

Client-Side Socket.IO Setup

  1. Install Socket.IO Client:
npm install socket.io-client 
Enter fullscreen mode Exit fullscreen mode
  1. 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; 
Enter fullscreen mode Exit fullscreen mode
  1. 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; 
Enter fullscreen mode Exit fullscreen mode
  1. 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 
Enter fullscreen mode Exit fullscreen mode

Step 3: Docker Compose for Deployment

  1. \** 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: 
Enter fullscreen mode Exit fullscreen mode
  1. 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"; } } 
Enter fullscreen mode Exit fullscreen mode

Step 4: Running the Application

  1. Build and start the services:
docker-compose up --build 
Enter fullscreen mode Exit fullscreen mode
  1. Access the application:
    • Frontend: http://localhost
    • Backend API: http://localhost/api

**GitHub : https://github.com/aixart12/colobrate-editor

Top comments (0)