DEV Community

Cover image for I Built and Deployed a Full Food Delivery Website With Pure HTML, CSS, and JavaScript
Alhan Bellary
Alhan Bellary

Posted on

I Built and Deployed a Full Food Delivery Website With Pure HTML, CSS, and JavaScript

I Built and Deployed a Full Food Delivery Website With Pure HTML, CSS, and JavaScript

By Alhan Bellary · GitHub: @alhaannn · Python Developer · Hubballi, India


Every frontend tutorial ends the same way: "Now deploy it!" — and then either doesn't tell you how, or points you to a paid service. I wanted to build something real, deploy it for free, and have it actually work for a real use case in my city.

The result is CraveCourier — a fully functional food delivery website for Hubballi, Karnataka. It's live at cravecourier-website.netlify.app, built with zero frameworks, zero backend, and zero cost to run. Just HTML5, CSS3, and vanilla JavaScript.

In this post I'll walk through the full architecture, every key design decision, and why I chose a pure frontend approach over the more "correct" backend-first solution.


Why No Backend?

The obvious question: a food delivery site needs order management, a database, admin tools — how does it work without a backend?

The honest answer is: for a portfolio project or MVP, it doesn't need one yet. What it needs is proof that the UX is right — that users can browse, build a cart, customise their order, and check out. That core loop can be validated entirely in the browser.

The architecture uses LocalStorage for everything that would normally require a server:

  • User accounts — stored as JSON in LocalStorage on registration
  • Session state — login status persisted across page refreshes via LocalStorage
  • Shopping cart — cart items maintained in LocalStorage during the session

This is not production auth. But it demonstrates the complete user journey end-to-end — registration, login, session persistence, protected actions, cart management, checkout — which is what a portfolio project needs to show.


File Structure

The project is deliberately minimal:

CraveCourier/
├── index.html      # Everything — nav, hero, menu, cart modal, auth modal
├── style.css       # Full theming, responsive layout, all animations
├── script.js       # Auth system + cart logic + all UI interactions
└── images/         # Logo (SVG) + 12 food photos
Enter fullscreen mode Exit fullscreen mode

No build step. No npm. No webpack. Open index.html in any browser and it works.


The Color System

The design uses a dark theme based on #0F2027 — a deep navy-black — with pure white text and a warm amber accent for CTAs. This combination is deliberate:

  • Dark backgrounds make food photography pop — the colors in the images become the visual focus
  • High contrast improves readability on mobile in variable lighting conditions
  • Warm accent (#F5A623) is used only for primary actions (Add to Cart, Checkout, Login) — it draws the eye to exactly the right places
:root {
    --bg-primary: #0F2027;
    --bg-card: #1a2a3a;
    --text-primary: #ffffff;
    --text-secondary: #b0bec5;
    --accent: #F5A623;
    --success: #2ecc71;
    --danger: #e74c3c;
}
Enter fullscreen mode Exit fullscreen mode

Every color in the site comes from this palette. Consistency at this level is what makes a UI feel designed rather than assembled.


Authentication System — LocalStorage Without a Backend

The auth system implements the full login/register flow using LocalStorage as the data store:

// Registration
function registerUser(name, email, password) {
    const users = JSON.parse(localStorage.getItem('users') || '[]');

    // Check for existing email
    if (users.find(u => u.email === email)) {
        showNotification('Email already registered', 'error');
        return false;
    }

    users.push({ name, email, password, createdAt: Date.now() });
    localStorage.setItem('users', JSON.stringify(users));
    return true;
}

// Login
function loginUser(email, password) {
    const users = JSON.parse(localStorage.getItem('users') || '[]');
    const user = users.find(u => u.email === email && u.password === password);

    if (user) {
        localStorage.setItem('currentUser', JSON.stringify({ name: user.name, email }));
        updateUIForLoggedInUser(user.name);
        return true;
    }
    return false;
}

// Session persistence on page load
function checkExistingSession() {
    const current = JSON.parse(localStorage.getItem('currentUser') || 'null');
    if (current) {
        updateUIForLoggedInUser(current.name);
    }
}
Enter fullscreen mode Exit fullscreen mode

What this correctly demonstrates:

  • Duplicate email prevention on registration
  • Session persistence across page refreshes (checkExistingSession runs on every load)
  • Protected actions — addToCart() checks localStorage.getItem('currentUser') before allowing adds
  • User name display in the header after login

What this intentionally omits: password hashing (bcrypt belongs on a server), server-side session validation, CSRF protection. These are backend concerns and would be the first things added when connecting a real backend.


Shopping Cart — Real-Time Updates

The cart is a JavaScript object array maintained in a module-scoped variable and reflected into the DOM on every update:

let cart = [];

function addToCart(name, price, customNote = '') {
    const currentUser = localStorage.getItem('currentUser');
    if (!currentUser) {
        showAuthModal();
        showNotification('Please login to add items to cart', 'error');
        return;
    }

    const existingItem = cart.find(item => item.name === name);
    if (existingItem) {
        existingItem.quantity++;
    } else {
        cart.push({ name, price, quantity: 1, note: customNote });
    }

    updateCartUI();
    showNotification(`${name} added to cart!`, 'success');
}

function updateCartUI() {
    const count = cart.reduce((sum, item) => sum + item.quantity, 0);
    const total = cart.reduce((sum, item) => sum + (item.price * item.quantity), 0);

    document.getElementById('cart-count').textContent = count;
    document.getElementById('cart-total').textContent = `₹${total}`;
    renderCartItems();
}
Enter fullscreen mode Exit fullscreen mode

Key design decisions:

  • Quantity stacking — adding the same item again increments quantity instead of creating a duplicate entry
  • Protected add — if the user isn't logged in, addToCart redirects them to the auth modal instead of silently failing
  • Real-time totalupdateCartUI() is called on every mutation so the header cart count and the cart modal total are always in sync
  • Custom notes — every item accepts a text note for order customisation, stored alongside the cart item

CSS Architecture — No Framework Needed

The layout uses CSS Grid for the page sections and Flexbox for component-level alignment. The responsive breakpoints handle three viewport sizes:

/* Menu grid — 3 columns on desktop */
.menu-grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 2rem;
}

/* 2 columns on tablet */
@media (max-width: 768px) {
    .menu-grid {
        grid-template-columns: repeat(2, 1fr);
    }
}

/* 1 column on mobile */
@media (max-width: 480px) {
    .menu-grid {
        grid-template-columns: 1fr;
    }
}
Enter fullscreen mode Exit fullscreen mode

The animation system uses CSS transitions with transform (not top/left) for performance. Hover effects on cards and buttons use transform: translateY(-4px) — GPU-composited, no layout reflow:

.food-card {
    transition: transform 0.3s ease, box-shadow 0.3s ease;
}

.food-card:hover {
    transform: translateY(-8px);
    box-shadow: 0 20px 40px rgba(0,0,0,0.3);
}
Enter fullscreen mode Exit fullscreen mode

Notification system — success/error toasts slide in from the top right using a CSS class toggle:

.notification {
    position: fixed;
    top: 20px;
    right: 20px;
    transform: translateX(200%);
    transition: transform 0.4s ease;
}

.notification.show {
    transform: translateX(0);
}
Enter fullscreen mode Exit fullscreen mode

JavaScript adds the show class, waits 3 seconds, removes it. The CSS handles the animation entirely.


Modal System — No Library Required

Both the auth modal (login/register) and the cart modal are built without any JavaScript modal library. The pattern is a fixed overlay <div> that starts with display: none and becomes display: flex on open:

function showCartModal() {
    document.getElementById('cart-modal').style.display = 'flex';
    document.body.style.overflow = 'hidden';  // Prevent background scroll
    renderCartItems();
}

function closeCartModal() {
    document.getElementById('cart-modal').style.display = 'none';
    document.body.style.overflow = '';
}

// Close on backdrop click
document.getElementById('cart-modal').addEventListener('click', function(e) {
    if (e.target === this) closeCartModal();
});
Enter fullscreen mode Exit fullscreen mode

Body scroll lock (overflow: hidden) is critical for modals — without it, users can scroll the background content while the modal is open, which is disorienting.

Backdrop click to close is implemented by checking e.target === this — if the click target is the overlay itself (not a child element), close the modal. One line, no library.


Deployment on Netlify

The deployment is a drag-and-drop: zip the project folder, drop it on netlify.com/drop. Netlify assigns a URL and handles HTTPS automatically. For a static site with no build step, this is genuinely the fastest path from code to live URL.

Live site: cravecourier-website.netlify.app


What I'd Do Differently for a Real Production Version

The LocalStorage auth is the obvious first thing to replace. A real version would use:

  • Django backend with django.contrib.auth for user management and sessions
  • PostgreSQL for orders, users, and menu items
  • Django REST Framework API consumed by the frontend
  • Stripe or Razorpay for payment processing

The frontend JavaScript would stay largely the same — fetch() calls to the API replace the LocalStorage reads. The cart logic, animation system, and CSS are production-ready as-is.


Full Source Code

Everything is on GitHub, and the site is live:

👉 github.com/alhaannn/CraveCourier-Website
🌐 cravecourier-website.netlify.app


About the Author

I'm Alhan Bellary (@alhaannn), a Python and frontend developer from Hubballi, Karnataka, India.

Other projects:

Certifications: Oracle Certified Data Science Professional · IBM Certified AI Literacy


Tags: javascript · html · css · web-development · netlify · frontend · food-delivery · localstorage · vanilla-js · alhan-bellary · alhaannn

Top comments (0)