DEV Community

mobin mollapor
mobin mollapor

Posted on

Why We Chose Django + Flutter for Our Marketplace Platform (And What We Learned)

Why We Chose Django + Flutter for Our Marketplace Platform (And What We Learned)

Originally published at niqow.ir


When we started building Niqow, a software development team specializing in e-commerce and platform solutions, we had a simple question:

What's the right tech stack for a multi-vendor marketplace that needs to scale from 10 sellers to 10,000?

We evaluated several options — Node.js, Laravel, and even Golang for the backend. In the end, we chose Django for the backend and Flutter for the mobile app. This is a practical look at why, and what we learned building a real marketplace platform on this stack.

The Requirements

Before choosing a stack, we defined what the platform actually needed to do:

  • Multi-tenancy: Each seller has isolated data, but shares the same database
  • Real-time messaging: Buyers and sellers need to chat in real time
  • Video consultations: Some sellers offer consultation sessions
  • Commission-based payments: The platform takes a cut from every transaction
  • Multi-platform access: Web + Android + iOS

This is not a simple CRUD app. It's a system where data isolation, transaction integrity, and real-time communication all matter.

Why Django for the Backend

We chose Django for three specific reasons:

1. Built-in Multi-Tenancy Patterns

Django's ORM makes it straightforward to implement row-level multi-tenancy:

class Seller(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    shop_name = models.CharField(max_length=200)
    commission_rate = models.DecimalField(max_digits=5, decimal_places=2)

class Product(models.Model):
    seller = models.ForeignKey(Seller, on_delete=models.CASCADE)
    name = models.CharField(max_length=200)
    
    class Meta:
        indexes = [
            models.Index(fields=['seller', 'is_active']),
        ]

Every query is automatically scoped by seller. No shared data leaks.

2. Django Channels for Real-Time Features

For the built-in messaging system between buyers and sellers, we used Django Channels with WebSocket support:

class ChatConsumer(AsyncWebsocketConsumer):
    async def connect(self):
        self.room_id = self.scope['url_route']['kwargs']['room_id']
        await self.channel_layer.group_add(
            f"chat_{self.room_id}",
            self.channel_name
        )
        await self.accept()
    
    async def receive(self, text_data):
        message = json.loads(text_data)
        await self.channel_layer.group_send(
            f"chat_{self.room_id}",
            {'type': 'chat_message', 'message': message['text']}
        )

Real-time messaging without leaving the Django ecosystem.

3. DRF for API Development

Django REST Framework gave us a consistent way to expose APIs to both the web frontend and the Flutter mobile app:

class ProductViewSet(viewsets.ModelViewSet):
    serializer_class = ProductSerializer
    permission_classes = [IsSellerOrReadOnly]
    
    def get_queryset(self):
        if self.request.user.is_seller:
            return Product.objects.filter(seller=self.request.user.seller)
        return Product.objects.filter(is_active=True)

One API, three clients (web, Android, iOS).

Why Flutter for Mobile

We chose Flutter over React Native for three reasons:

  1. Single codebase for Android and iOS — critical for a small team
  2. Native performance — important for real-time chat and video
  3. Consistent UI — the same experience across platforms

The Flutter app communicates with the Django backend via REST APIs and WebSockets.

The Architecture

Here's the overall architecture:

┌─────────────┐    ┌─────────────┐    ┌─────────────┐
│   Web App   │    │  Flutter    │    │  Flutter    │
│  (Next.js)  │    │  (Android)  │    │   (iOS)     │
└──────┬──────┘    └──────┬──────┘    └──────┬──────┘
       │                  │                  │
       └──────────────────┼──────────────────┘
                          │
                    ┌─────▼─────┐
                    │  Django   │
                    │  + DRF    │
                    │ + Channels│
                    └─────┬─────┘
                          │
              ┌───────────┼───────────┐
              │           │           │
        ┌─────▼────┐ ┌────▼────┐ ┌────▼────┐
        │PostgreSQL│ │  Redis  │ │  MinIO  │
        └──────────┘ └─────────┘ └─────────┘

What We Learned

Lesson 1: Multi-Tenancy Is Harder Than It Looks

Row-level multi-tenancy works, but you have to be disciplined. Every query must be scoped by tenant. We solved this with a custom manager:

class TenantManager(models.Manager):
    def for_seller(self, seller):
        return self.filter(seller=seller)

Lesson 2: Real-Time Chat Needs Careful Design

Django Channels works, but scaling WebSockets requires a message broker. We used Redis as the channel layer backend:

CHANNEL_LAYERS = {
    "default": {
        "BACKEND": "channels_redis.core.RedisChannelLayer",
        "CONFIG": {"hosts": [("redis", 6379)]},
    },
}

Lesson 3: Commission Logic Should Be a Service, Not a Signal

We initially used Django signals to calculate commissions. Big mistake — signals are hard to test and debug. We refactored into an explicit service:

class CommissionService:
    @staticmethod
    def calculate(order):
        commission = order.total * order.seller.commission_rate / 100
        Commission.objects.create(
            seller=order.seller,
            order=order,
            amount=commission,
            status='pending'
        )
        return commission

Now it's testable, traceable, and deterministic.

Lesson 4: Choose Boring Technology

We didn't use Kubernetes. We didn't use microservices. We used Docker Compose and a monolith. This wasn't a limitation — it was a decision that let us ship faster and debug easier.

The Result

The platform — which we call MartLink — now handles:

  • Multi-vendor product management
  • Real-time buyer-seller chat
  • Video consultation sessions
  • Automated commission settlement
  • Mobile apps for Android and iOS

It's not the most elegant architecture, but it works, it scales, and it ships.

Conclusion

If you're building a marketplace platform, here's what I'd suggest:

  • Django + DRF + Channels is a solid backend foundation
  • Flutter is a great choice for multi-platform mobile
  • PostgreSQL + Redis + MinIO covers 95% of your infrastructure needs
  • Monolith first, microservices later — don't over-engineer

You can see what we built at Niqow. If you're working on a similar platform, feel free to reach out — we're always happy to talk architecture.


What stack are you using for your marketplace platform? Share your experience in the comments.

Top comments (0)