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

Dhruv Kumar @aixart

About: I am passionate about solving complex problems, enhancing user experience, and developing innovative solutions. I am always eager to learn new technologies.

Location:
Bengaluru ,Karnataka
Joined:
May 28, 2023

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

Publish Date: Dec 21 '24
2 0

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

Comments 0 total

    Add comment