MEHDI.
RETURN_TO_INDEX

Building Tastify: A Real-Time Restaurant ERP with Sentiment Analysis

7 min read
#React#Redis#Celery#WebSockets#Hugging Face#Django#NLP

Tastify started as an end-of-year academic project and grew into something more complex than any of us initially expected. The goal was to build a complete restaurant management system: not just a menu website, but a platform that handles orders, kitchen operations, inventory, reservations, payments and customer feedback. We also wanted to add sentiment analysis on customer reviews, which turned out to be one of the most interesting technical challenges.

This was a collaborative academic project, so I want to be clear about the context: Tastify is not a product running in real restaurants. It is a working prototype that taught us a lot about full-stack architecture, real-time systems and the gap between a feature list and a functioning application.

The Business Problem

A restaurant has multiple operational zones that need to coordinate. The front of house takes orders and manages tables. The kitchen receives those orders, prioritizes them, and marks them as ready. The manager tracks inventory, reviews revenue, and monitors customer satisfaction. Traditionally, these functions live in separate systems or on paper.

Tastify attempts to unify them. A waiter creates an order on a tablet. The kitchen sees it immediately on a Kitchen Display System. When the order is ready, the waiter gets notified. Meanwhile, the manager can see which items are selling, which tables are occupied, and whether recent customer reviews are trending negative.

Architecture

The backend is built with Django REST Framework, exposing a REST API for most operations and Django Channels with WebSockets for real-time updates. The frontend is a React single-page application written in TypeScript. The database is MySQL, with Redis serving as both the channel layer for WebSockets and the message broker for Celery.

The reason for this separation is straightforward: HTTP is fine for creating a menu item or fetching a report, but when a kitchen staff member marks an order as "ready," the waiter should not have to refresh the page to see it. WebSockets solve that problem.

The Staff Interface

The staff application handles day-to-day operations:

  • Menu management: Create, edit and organize menu items with categories, prices and availability status
  • Table management: Assign tables, track occupancy, and manage table-specific orders
  • Order workflow: Create orders, add items, modify quantities, and track status from "pending" to "served"
  • Kitchen Display System: A dedicated view for kitchen staff showing incoming orders, sorted by priority and time
  • Inventory tracking: Monitor stock levels and get alerts when ingredients run low
  • Reservations: Manage bookings with date, time, party size and special requests
  • Payments: Record payment method and amount for completed orders
  • Loyalty program: Track customer visits and reward points

Each staff role (waiter, kitchen staff, manager) sees a different interface with only the functions they need.

The Customer Portal

Customers get a separate interface where they can:

  • Browse the menu
  • View their order history
  • Leave reviews with ratings and text
  • Check their loyalty points
  • Manage their profile

The customer portal is deliberately simpler than the staff interface. It does not need table management or inventory features.

Real-Time Updates with WebSockets

The WebSocket implementation uses Django Channels with Redis as the channel layer. When an order status changes, the kitchen staff's display updates immediately. When a new reservation comes in, the front-of-house system reflects it without polling.

# Simplified example of sending an order update
async def update_order_status(order_id, new_status):
    order = await get_order(order_id)
    order.status = new_status
    await order.asave()

    await channel_layer.group_send(
        f"kitchen_{order.restaurant_id}",
        {
            "type": "order.status_update",
            "order_id": order_id,
            "status": new_status,
        },
    )

The WebSocket connection also handles presence (which staff members are online) and notifications (new orders, low stock alerts). This required careful handling of connection states, reconnection logic, and message serialization.

Asynchronous Tasks with Celery

Not everything needs to happen in real time. Sending email notifications, generating reports, and processing customer reviews are all tasks that can run in the background. Celery handles these, with Redis as the message broker.

The most important background task is sentiment analysis.

Sentiment Analysis Pipeline

When a customer submits a review, it goes through an asynchronous processing pipeline:

  1. The review text is received and stored
  2. A Celery task is queued to analyze the sentiment
  3. The task uses a Hugging Face model to classify the text as positive, negative or neutral
  4. The result is stored alongside the review

The challenge is multilingual. Customers might leave reviews in French, English, or Arabic, sometimes mixing languages in the same sentence. We used a multilingual sentiment model that handles this without requiring explicit language detection.

For cases where the Hugging Face API is unavailable (rate limits, network issues, or the model service being down), we implemented a local fallback system. It uses a simpler keyword-based approach that is less accurate but ensures the pipeline does not block entirely. The fallback is not as good as the transformer model, but it is better than having reviews sit unprocessed indefinitely.

@shared_task
def analyze_review_sentiment(review_id):
    review = Review.objects.get(id=review_id)

    try:
        result = sentiment_pipeline(review.text)
        review.sentiment = result["label"]
        review.sentiment_score = result["score"]
    except Exception:
        # Fallback to keyword-based analysis
        review.sentiment = fallback_analyze(review.text)
        review.sentiment_score = None

    review.save()

The manager dashboard shows sentiment trends over time, with filters for language and rating. A sudden spike in negative reviews about a specific menu item is visible at a glance.

What I Learned

Scope creeps fast in a multi-role application. Every additional user role (waiter, kitchen staff, manager, customer) multiplies the complexity. Permissions, views, workflows, and edge cases all grow non-linearly.

Real-time is not free. WebSockets add complexity at every layer: connection management, message serialization, error handling, and testing. For features where a 5-second delay is acceptable, polling might be simpler.

Background processing changes your data model. When a review is submitted, its sentiment is not immediately available. The UI needs to handle the "pending analysis" state gracefully. This required adding fields and conditional rendering that would not exist in a synchronous system.

Sentiment analysis is useful when it answers a real question. We did not add it because "AI" sounds impressive in a project description. We added it because a manager reading 200 reviews cannot manually identify which menu items are generating complaints. The sentiment analysis automates that triage.

Tastify is the most complex project I have worked on so far. It is not perfect, and there are features I would implement differently with more experience. But it taught me that building a working system and building a useful system are two very different things.