Back to Projects

LancersHub

Microservices-based freelancing platform connecting clients with skilled freelancers

Role: Backend Developer
Timeline: Jan 2025 - Apr 2025
Status: Live
LancersHub project screenshot

01. Overview

LancersHub is a comprehensive freelancing platform designed to connect clients with skilled freelancers. Built with a microservices architecture, the platform enables seamless project management, secure payment processing, and real-time communication between parties. The system consists of four independent microservices (User, Message, Project, Payment), each with its own PostgreSQL database, orchestrated through Docker Compose and fronted by an Nginx API Gateway.

The platform addresses the need for a modern, scalable solution for freelance work management. It separates concerns across independent services, allowing for better maintainability, scalability, and the ability to deploy and update services independently. The architecture includes real-time messaging via Socket.IO, a project proposal system, payment processing with earnings tracking, dashboard analytics with financial predictions, and MinIO object storage for file management. This architecture ensures the platform can grow with user demand while maintaining performance and reliability.

02. Problems & Challenges

Building a microservices-based platform presented several significant challenges:

  • Service Communication: Coordinating communication between four independent services (User, Message, Project, Payment) was complex. Services needed to validate data across service boundaries (e.g., payment service validating users and projects), requiring HTTP calls through the Nginx gateway while maintaining loose coupling and handling failures gracefully.
  • Database Isolation: Each service has its own PostgreSQL database with separate users and schemas. This required careful database initialization, proper connection pooling, and ensuring data isolation while still allowing services to reference data in other services (e.g., project_id, user_id foreign keys).
  • JWT Authentication Across Services: Implementing a unified authentication system where each service independently validates JWT tokens was challenging. The User service issues tokens, but all services need to validate them without sharing sessions or making repeated calls to the User service.
  • Real-Time Messaging in Microservices: Implementing Socket.IO for real-time messaging required careful design to handle WebSocket connections through the Nginx gateway, manage socket-to-user mappings across service boundaries, and ensure messages are delivered to the correct participants even when they're connected through different instances.
  • File Storage Integration: Integrating MinIO (S3-compatible object storage) for profile pictures and other files required handling presigned URLs, bucket management, and ensuring files are accessible across the microservices architecture.
  • Nginx API Gateway Configuration: Configuring Nginx as an API gateway to route requests to the correct services, handle WebSocket upgrades, proxy headers correctly, and serve the React frontend required careful configuration of location blocks and proxy settings.
  • Docker Compose Orchestration: Managing multiple services with Docker Compose required handling service dependencies, database initialization timing, environment variable management, and ensuring services start in the correct order.
  • Cross-Service Data Validation: When the Payment service creates a payment, it needs to validate that the project and users exist by calling the Project and User services. This required implementing robust error handling and ensuring services handle partial failures gracefully.

Critical Challenge: Inter-Service Communication & Data Consistency

One of the most complex challenges was handling operations that require data from multiple services. For example, when creating a payment, the Payment service needs to validate that the project exists (Project service), the freelancer exists (User service), and the client exists (User service). If any validation fails, the payment creation must fail. Additionally, when a payment is created, the Project service needs to be notified to mark the project as completed. This required careful error handling and ensuring that partial failures don't leave the system in an inconsistent state.

03. Solutions & Implementation

I addressed these challenges through careful architectural decisions and implementation patterns:

  • Nginx API Gateway: Implemented Nginx as a reverse proxy and API gateway that routes requests to appropriate services. It handles routing for `/api/users`, `/api/projects`, `/api/payments`, `/api/chats`, and WebSocket connections for `/sockets/chats`. This provides a single entry point for clients and simplifies service discovery.
  • Direct HTTP Inter-Service Communication: Services communicate via HTTP using Axios, calling each other through the Nginx gateway. For example, the Payment service validates users and projects by making HTTP GET requests to the User and Project services. This approach is simpler than event-driven architecture for this use case and provides immediate feedback.
  • JWT-Based Authentication: Implemented a centralized authentication in the User service that issues JWT tokens (access and refresh tokens). All services validate these tokens independently using the same JWT secret, eliminating the need for session sharing while maintaining security. Each service has its own `authMiddleware` that validates tokens.
  • Database Per Service: Each microservice has its own PostgreSQL database with separate database users and schemas. This ensures data isolation, allows independent scaling, and prevents tight coupling. The `init.sql` script creates all databases and users with proper permissions.
  • Docker Compose Orchestration: Used Docker Compose to orchestrate all services, databases, MinIO, and Nginx. Each service runs in its own container with proper dependencies, environment variables, and network configuration. Services wait for PostgreSQL to be ready before starting.
  • MinIO Object Storage: Integrated MinIO (S3-compatible) for file storage, used for profile pictures and other user files. The User service handles file uploads using Multer, stores files in MinIO buckets, and generates presigned URLs for secure file access.
  • Socket.IO Real-Time Messaging: Implemented real-time messaging in the Message service using Socket.IO. The service manages socket connections, maps sockets to users, and broadcasts messages to chat participants. WebSocket connections are proxied through Nginx with proper upgrade headers.
  • Service Helper Functions: Created helper functions in each service for inter-service communication. For example, `userService.js` in the Message service provides `getUserInfo`, `isUserValid`, and `getUserIds` functions that make HTTP calls to the User service. This encapsulates service communication logic.

Inter-Service Communication Pattern

When the Payment service creates a payment, it validates data across multiple services before creating the payment record. Here's how it works:

paymentController.js
// Payment service validates across services
exports.createPayment = async (req, res) => {
  const { project_id, freelancer_id, amount } = req.body;
  const authHeader = req.headers["authorization"];

  // Validate across services in parallel
  const [projectExists, freelancerExists, clientExists] = 
    await Promise.all([
      isProjectValid(project_id, authHeader),
      isUserValid(freelancer_id, authHeader),
      isUserValid(req.user.id, authHeader)
    ]);

  if (!projectExists || !freelancerExists || !clientExists) {
    return res.status(400).json({ error: "Invalid data" });
  }

  // Create payment
  const newPayment = await createPayment(project_id, freelancer_id, 
    req.user.id, amount, "pending");

  // Notify Project service to mark as completed
  await markProjectAsCompleted(project_id, authHeader);

  return res.status(201).json(newPayment);
};

04. Key Learnings

This project provided deep insights into microservices architecture:

  • Microservices Trade-offs: I learned that microservices aren't always the right choice. They add complexity in exchange for scalability and maintainability. Understanding when to use them versus a monolith is crucial. For this project, the separation of concerns (user management, messaging, projects, payments) made microservices a good fit.
  • Database Per Service Pattern: Implementing separate databases for each service taught me about data isolation, independent scaling, and the challenges of maintaining referential integrity across service boundaries. I learned to use service calls for validation rather than foreign keys.
  • API Gateway Pattern: Using Nginx as an API gateway taught me about routing, load balancing, WebSocket proxying, and how to provide a single entry point for clients while hiding the complexity of multiple backend services.
  • Inter-Service Communication: I learned about synchronous HTTP communication between services, handling failures gracefully, and the importance of proper error handling when services depend on each other. I also learned when to use direct calls vs. event-driven patterns.
  • JWT Authentication in Microservices: Implementing JWT authentication across services taught me about stateless authentication, token validation, and how to share secrets securely across services without coupling them.
  • Docker Compose Orchestration: Working with Docker Compose taught me about service dependencies, environment variable management, network configuration, and how to orchestrate complex multi-service applications for development and deployment.
  • Object Storage Integration: Integrating MinIO for file storage taught me about S3-compatible APIs, presigned URLs, bucket management, and how to handle file uploads in a microservices architecture where files need to be accessible across services.
  • Real-Time Features in Microservices: Implementing Socket.IO for real-time messaging taught me about WebSocket proxying through Nginx, managing socket connections across service boundaries, and how to map sockets to users for message delivery.
  • Service Boundaries & Data Ownership: I learned the importance of properly defining service boundaries and data ownership. Each service owns its data and exposes it through APIs. Other services access this data through service calls, not direct database access.

Major Takeaway

The biggest lesson was understanding that microservices require a different mindset than monolithic applications. You can't just split a monolith into services - you need to think about service boundaries, data ownership, communication patterns, and failure modes from the start. I learned that not every microservices pattern (like Saga) is necessary for every use case - sometimes simpler direct HTTP calls are more appropriate. The key is understanding the trade-offs and choosing the right pattern for the problem. This project significantly improved my understanding of distributed systems, container orchestration, and scalable architecture.

05. Tech Stack

Frontend

React 19 JavaScript (ES6+) TailwindCSS Axios Socket.IO Client React Router Recharts

Backend Services

Node.js Express PostgreSQL Socket.IO JWT Bcrypt Sequelize

Infrastructure

Docker Docker Compose Nginx MinIO Multer

Tools & Libraries

Express Validator JWT Decode Git

06. Results & Impact

LancersHub successfully demonstrates enterprise-level microservices architecture:

  • Four Independent Microservices: Successfully deployed User, Message, Project, and Payment services, each with its own PostgreSQL database, working together seamlessly through the Nginx API gateway. Services communicate via HTTP while maintaining loose coupling.
  • Real-Time Messaging System: Implemented Socket.IO-based real-time messaging that works across the microservices architecture. Users can send messages, join chats, and receive real-time updates. The system manages socket connections and maps them to users for proper message delivery.
  • Project Proposal System: Built a comprehensive proposal system where freelancers can submit proposals for projects, clients can accept/reject proposals, and projects transition through states (open, in progress, closed). The system handles proposal updates and project assignment.
  • Payment Processing & Analytics: Implemented secure payment processing with earnings tracking, monthly earnings aggregation, payment status management, and comprehensive dashboard analytics. The system includes financial prediction utilities that forecast earnings based on historical data.
  • Dashboard with Analytics: Created a rich dashboard with Recharts visualizations including monthly income charts, payment status pie charts, earnings summaries, and financial predictions. The dashboard provides insights for both clients and freelancers.
  • File Storage Integration: Integrated MinIO (S3-compatible) object storage for profile pictures and other files. The system handles file uploads, generates presigned URLs for secure access, and manages buckets automatically.
  • JWT Authentication Across Services: Implemented stateless JWT authentication where the User service issues tokens and all services validate them independently. The system supports access and refresh tokens with proper expiration handling.
  • Docker Compose Orchestration: Successfully orchestrated all services, databases, MinIO, and Nginx using Docker Compose. The system handles service dependencies, database initialization, and proper startup sequencing.
  • Nginx API Gateway: Configured Nginx as a reverse proxy and API gateway that routes requests to appropriate services, handles WebSocket upgrades, and serves the React frontend. The gateway provides a single entry point while hiding service complexity.
  • Scalable Architecture: Created a scalable architecture where services can be independently scaled, updated, and deployed. Each service owns its data and exposes it through well-defined APIs.

The project showcases my ability to design and implement complex distributed systems, understand trade-offs between different architectural patterns, and build scalable, maintainable applications. It demonstrates expertise in microservices architecture, container orchestration, inter-service communication, real-time systems, and full-stack development. The platform is production-ready and demonstrates enterprise-level software engineering practices.

Screenshot