Logo
Back to insights
1/5/2024 10 min readReal-time Development

TeamFlow: Custom Next.js API Routes with Socket.IO Integration

Saif
Saif ur RehmanNext-Gen Architect
TeamFlow: Custom Next.js API Routes with Socket.IO Integration

TeamFlow: Custom Next.js API Routes with Socket.IO Integration

Building TeamFlow required creating a sophisticated real-time collaboration platform that seamlessly integrates Socket.IO with Next.js API routes for optimal performance and scalability.

The Challenge

TeamFlow needed to provide:

  • Real-time team collaboration features
  • Custom API routes for complex business logic
  • WebSocket integration for live updates
  • Scalable architecture for multiple teams
  • Secure authentication and authorization

Custom Next.js API Architecture

1. Base API Route Handler

// pages/api/base/[endpoint].js
import { withAuth } from '../../../middleware/auth';
import { withValidation } from '../../../middleware/validation';
import { withRateLimit } from '../../../middleware/rateLimit';

export default withAuth(withValidation(withRateLimit(async (req, res) => {
  const { method } = req;
  const { endpoint } = req.query;
  
  try {
    switch (method) {
      case 'GET':
        return await handleGet(req, res, endpoint);
      case 'POST':
        return await handlePost(req, res, endpoint);
      case 'PUT':
        return await handlePut(req, res, endpoint);
      case 'DELETE':
        return await handleDelete(req, res, endpoint);
      default:
        return res.status(405).json({ error: 'Method not allowed' });
    }
  } catch (error) {
    console.error('API Error:', error);
    return res.status(500).json({ error: 'Internal server error' });
  }
})));

2. Custom Middleware Stack

// middleware/auth.js
export const withAuth = (handler) => async (req, res) => {
  const token = req.headers.authorization?.replace('Bearer ', '');
  
  if (!token) {
    return res.status(401).json({ error: 'No token provided' });
  }
  
  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET);
    req.user = decoded;
    return handler(req, res);
  } catch (error) {
    return res.status(401).json({ error: 'Invalid token' });
  }
};

// middleware/validation.js
export const withValidation = (schema) => (handler) => async (req, res) => {
  try {
    await schema.validateAsync(req.body);
    return handler(req, res);
  } catch (error) {
    return res.status(400).json({ 
      error: 'Validation failed', 
      details: error.details 
    });
  }
};

Socket.IO Integration

1. Custom Socket Server Setup

// lib/socketServer.js
import { Server } from 'socket.io';
import { createServer } from 'http';
import { parse } from 'url';

class SocketServer {
  constructor(server) {
    this.io = new Server(server, {
      cors: {
        origin: process.env.CLIENT_URL,
        methods: ["GET", "POST"]
      },
      transports: ['websocket', 'polling']
    });
    
    this.setupMiddleware();
    this.setupEventHandlers();
  }
  
  setupMiddleware() {
    this.io.use(async (socket, next) => {
      try {
        const token = socket.handshake.auth.token;
        const user = await this.verifyToken(token);
        socket.user = user;
        next();
      } catch (error) {
        next(new Error('Authentication failed'));
      }
    });
  }
}

2. Real-time Event Handlers

// lib/eventHandlers.js
export class EventHandlers {
  constructor(io, socket) {
    this.io = io;
    this.socket = socket;
    this.setupHandlers();
  }
  
  setupHandlers() {
    this.socket.on('join-team', this.handleJoinTeam.bind(this));
    this.socket.on('leave-team', this.handleLeaveTeam.bind(this));
    this.socket.on('send-message', this.handleSendMessage.bind(this));
    this.socket.on('update-project', this.handleUpdateProject.bind(this));
    this.socket.on('disconnect', this.handleDisconnect.bind(this));
  }
  
  async handleJoinTeam(data) {
    const { teamId } = data;
    
    // Validate team access
    const hasAccess = await this.validateTeamAccess(teamId);
    if (!hasAccess) {
      this.socket.emit('error', { message: 'Access denied' });
      return;
    }
    
    // Join team room
    this.socket.join(`team-${teamId}`);
    
    // Notify other team members
    this.socket.to(`team-${teamId}`).emit('user-joined', {
      user: this.socket.user,
      timestamp: new Date()
    });
    
    // Send current team state
    const teamState = await this.getTeamState(teamId);
    this.socket.emit('team-state', teamState);
  }
  
  async handleSendMessage(data) {
    const { teamId, message, type = 'text' } = data;
    
    // Validate team membership
    if (!this.socket.rooms.has(`team-${teamId}`)) {
      this.socket.emit('error', { message: 'Not in team' });
      return;
    }
    
    // Process message
    const processedMessage = {
      id: generateId(),
      user: this.socket.user,
      content: message,
      type,
      timestamp: new Date(),
      teamId
    };
    
    // Save to database
    await this.saveMessage(processedMessage);
    
    // Broadcast to team
    this.io.to(`team-${teamId}`).emit('new-message', processedMessage);
  }
}

Advanced API Route Patterns

1. Dynamic Route Handling

// pages/api/teams/[teamId]/[action].js
export default async function handler(req, res) {
  const { teamId, action } = req.query;
  const { method } = req;
  
  // Route to specific action handler
  const actionHandlers = {
    'members': handleMembers,
    'projects': handleProjects,
    'settings': handleSettings,
    'analytics': handleAnalytics
  };
  
  const handler = actionHandlers[action];
  if (!handler) {
    return res.status(404).json({ error: 'Action not found' });
  }
  
  return handler(req, res, teamId);
}

async function handleMembers(req, res, teamId) {
  switch (req.method) {
    case 'GET':
      const members = await getTeamMembers(teamId);
      return res.json(members);
    case 'POST':
      const newMember = await addTeamMember(teamId, req.body);
      return res.json(newMember);
    case 'DELETE':
      await removeTeamMember(teamId, req.body.userId);
      return res.json({ success: true });
  }
}

2. Webhook Integration

// pages/api/webhooks/[service].js
export default async function handler(req, res) {
  const { service } = req.query;
  
  // Verify webhook signature
  const isValid = await verifyWebhookSignature(req, service);
  if (!isValid) {
    return res.status(401).json({ error: 'Invalid signature' });
  }
  
  // Process webhook based on service
  const processors = {
    'github': processGithubWebhook,
    'slack': processSlackWebhook,
    'trello': processTrelloWebhook
  };
  
  const processor = processors[service];
  if (!processor) {
    return res.status(404).json({ error: 'Service not supported' });
  }
  
  await processor(req.body);
  res.json({ success: true });
}

Performance Optimizations

1. Connection Pooling

// lib/connectionPool.js
class ConnectionPool {
  constructor(maxConnections = 10) {
    this.pool = [];
    this.maxConnections = maxConnections;
    this.activeConnections = 0;
  }
  
  async getConnection() {
    if (this.pool.length > 0) {
      return this.pool.pop();
    }
    
    if (this.activeConnections < this.maxConnections) {
      const connection = await createConnection();
      this.activeConnections++;
      return connection;
    }
    
    // Wait for available connection
    return new Promise((resolve) => {
      this.waitingQueue = this.waitingQueue || [];
      this.waitingQueue.push(resolve);
    });
  }
  
  releaseConnection(connection) {
    if (this.waitingQueue && this.waitingQueue.length > 0) {
      const resolve = this.waitingQueue.shift();
      resolve(connection);
    } else {
      this.pool.push(connection);
    }
  }
}

2. Caching Strategy

// lib/cache.js
class CacheManager {
  constructor() {
    this.memoryCache = new Map();
    this.redis = new Redis(process.env.REDIS_URL);
  }
  
  async get(key, fallback) {
    // Check memory cache first
    if (this.memoryCache.has(key)) {
      return this.memoryCache.get(key);
    }
    
    // Check Redis cache
    const cached = await this.redis.get(key);
    if (cached) {
      const data = JSON.parse(cached);
      this.memoryCache.set(key, data);
      return data;
    }
    
    // Execute fallback function
    const data = await fallback();
    await this.set(key, data);
    return data;
  }
  
  async set(key, data, ttl = 300) {
    this.memoryCache.set(key, data);
    await this.redis.setex(key, ttl, JSON.stringify(data));
  }
}

Real-time Features Implementation

1. Live Cursor Tracking

// components/LiveCursor.jsx
export const LiveCursor = ({ userId, position, color }) => {
  const [cursorPosition, setCursorPosition] = useState(position);
  
  useEffect(() => {
    const handleMouseMove = (e) => {
      const newPosition = { x: e.clientX, y: e.clientY };
      setCursorPosition(newPosition);
      
      // Broadcast cursor position
      socket.emit('cursor-move', {
        userId,
        position: newPosition,
        timestamp: Date.now()
      });
    };
    
    document.addEventListener('mousemove', handleMouseMove);
    return () => document.removeEventListener('mousemove', handleMouseMove);
  }, [userId]);
  
  return (
    
{userId}
); };

2. Real-time Document Collaboration

// lib/documentCollaboration.js
class DocumentCollaboration {
  constructor(socket, documentId) {
    this.socket = socket;
    this.documentId = documentId;
    this.operations = [];
    this.setupEventHandlers();
  }
  
  setupEventHandlers() {
    this.socket.on('document-operation', this.handleOperation.bind(this));
    this.socket.on('document-sync', this.handleSync.bind(this));
  }
  
  handleOperation(operation) {
    // Apply operation to local document
    this.applyOperation(operation);
    
    // Broadcast to other collaborators
    this.socket.to(`document-${this.documentId}`).emit('document-operation', operation);
  }
  
  applyOperation(operation) {
    switch (operation.type) {
      case 'insert':
        this.insertText(operation.position, operation.text);
        break;
      case 'delete':
        this.deleteText(operation.position, operation.length);
        break;
      case 'format':
        this.applyFormatting(operation.range, operation.format);
        break;
    }
  }
}

Results & Impact

TeamFlow's custom API routes and Socket.IO integration achieved:

  • Real-time collaboration with < 100ms latency
  • Scalable architecture supporting 1000+ concurrent users
  • 99.9% uptime with robust error handling
  • Seamless user experience across all devices

Key Learnings

  1. Custom API routes provide better control over business logic
  2. Socket.IO integration enables real-time features
  3. Middleware patterns improve code organization and reusability
  4. Performance optimization is crucial for real-time applications
  5. Error handling becomes more complex with WebSocket connections

TeamFlow demonstrates how custom Next.js API routes combined with Socket.IO can create powerful real-time collaboration platforms that scale effectively.

Technical Discussion

Have insights to share or a project that needs this level of engineering? Let's connect.

Read Next