Building GigFlow: A Real-Time Freelance Marketplace with Secure Hiring Logic

Published: (January 16, 2026 at 01:35 AM EST)
3 min read
Source: Dev.to

Source: Dev.to

Introduction

Modern marketplaces aren’t just about CRUD APIs — they’re about correctness, trust, and real‑time feedback. In this post I walk through how I built GigFlow, a full‑stack freelance marketplace where clients can post jobs, freelancers can bid, and hiring happens atomically and in real time.

Project Focus

  • Secure authentication
  • Correct hiring logic
  • Preventing race conditions
  • Real‑time updates using Socket.io

GigFlow Overview

  • Any authenticated user can post a gig (client role)
  • Any user can bid on gigs (freelancer role)
  • A client can hire exactly one freelancer per gig; all other bids are automatically rejected
  • The hired freelancer receives a real‑time notification

Tech Stack

LayerTechnology
FrontendReact (Vite), Tailwind CSS, Context API, Socket.io client, Fetch API with credentials
BackendNode.js + Express, MongoDB + Mongoose, JWT authentication (HttpOnly cookies), Socket.io, MongoDB transactions
DeploymentFrontend on Vercel, Backend on Render, Database on MongoDB Atlas

Authentication

Authentication uses JWT stored in HttpOnly cookies, which:

  • Prevents access from JavaScript (XSS‑safe)
  • Works cleanly with credentials: "include"

Each request:

  1. Verifies the JWT
  2. Attaches the authenticated user to req.user

This enables a role‑less design: users can act as both client and freelancer without separate accounts.

Data Models

// User
{
  "name": "String",
  "email": "String",
  "password": "HashedString"
}
// Gig
{
  "title": "String",
  "description": "String",
  "budget": "Number",
  "ownerId": "ObjectId",
  "status": "open | assigned",
  "assignedTo": "ObjectId | null"
}
// Bid
{
  "gigId": "ObjectId",
  "freelancerId": "ObjectId",
  "message": "String",
  "price": "Number",
  "status": "pending | hired | rejected"
}

Hiring Logic (Core Challenge)

Rule: Only one freelancer can be hired for a gig — ever.

Edge Case

Two clients (or two browser tabs) click Hire at the same time. Without protection, both bids could be marked as hired.

Solution: MongoDB Transaction

  1. Start a session
  2. Check if the gig is still open
  3. Update gig → set status to assigned and assignedTo to the selected freelancer
  4. Mark selected bidhired
  5. Mark all other bidsrejected
  6. Commit transaction

If any step fails, the transaction rolls back, guaranteeing:

  • Exactly one hired freelancer
  • No partial or inconsistent state

Real‑Time Notification

When a freelancer is hired:

  1. The backend emits a hired event to that freelancer’s Socket.io room.
  2. The freelancer’s dashboard updates instantly—no polling, no page refresh.

User Flow

  1. Create gig → gig status open
  2. View bids → freelancers submit bids
  3. Click Hire → gig becomes assigned
  4. **Freelancer receives real‑time hire notification`

State is kept minimal and predictable using the Context API.

Deployment Considerations

  • Set all required environment variables explicitly.
  • Configure CORS to allow the deployed frontend domain; Socket.io shares the same CORS settings as Express.
  • API base URL must point to the deployed backend.
  • Fetch requests must include credentials.

Key pillars

  • Correct API design
  • Secure authentication
  • Transactional integrity
  • Real‑time communication
  • Production deployment awareness

Live Demo & Source Code

  • Live App:
  • GitHub Repository:

Final Thoughts

The hardest part of full‑stack development isn’t writing code — it’s ensuring the system behaves correctly when multiple actions occur simultaneously. GigFlow was built with that principle in mind.

Back to Blog

Related posts

Read more »

𝗗𝗲𝘀𝗶𝗴𝗻𝗲𝗱 𝗮 𝗣𝗿𝗼𝗱𝘂𝗰𝘁𝗶𝗼𝗻‑𝗥𝗲𝗮𝗱𝘆 𝗠𝘂𝗹𝘁𝗶‑𝗥𝗲𝗴𝗶𝗼𝗻 𝗔𝗪𝗦 𝗔𝗿𝗰𝗵𝗶𝘁𝗲𝗰𝘁𝘂𝗿𝗲 𝗘𝗞𝗦 | 𝗖𝗜/𝗖𝗗 | 𝗖𝗮𝗻𝗮𝗿𝘆 𝗗𝗲𝗽𝗹𝗼𝘆𝗺𝗲𝗻𝘁𝘀 | 𝗗𝗥 𝗙𝗮𝗶𝗹𝗼𝘃𝗲𝗿

!Architecture Diagramhttps://dev-to-uploads.s3.amazonaws.com/uploads/articles/p20jqk5gukphtqbsnftb.gif I designed a production‑grade multi‑region AWS architectu...