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)