Logo

dev-resources.site

for different kinds of informations.

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

Published at
12/21/2024
Categories
flask
docker
redis
socketio
Author
Dhruv Kumar
Categories
4 categories in total
flask
open
docker
open
redis
open
socketio
open
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)
  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)
  1. Session and Redis Configuration:
import redis

cache = redis.StrictRedis(host=os.getenv('REDIS_HOST', 'redis'), port=6379, db=0)
  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

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
  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;
  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;
  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

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:
  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";
  }
}

Step 4: Running the Application

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

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

Featured ones: