Laser Inventory CRUD Project

Overview

Building a simple inventory management web app for laser-engraved products:

  • Backend: Django + Django REST Framework + PostgreSQL
  • Frontend: React (Vite scaffolded)
  • Goal: Track products (name, cost, price, stock) with CRUD functionality

1. Python Environment Setup

python3 -m venv venv
source venv/bin/activate
pip install django djangorestframework psycopg2-binary django-cors-headers
pip freeze > requirements.txt
  • venv: Isolated Python environment
  • django-cors-headers: For frontend-backend communication (CORS)

2. PostgreSQL Installation & Setup

curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt install postgresql postgresql-contrib libpq-dev
sudo systemctl status postgresql@15-main
sudo -u postgres psql

Create DB & User

CREATE USER engraveruser WITH PASSWORD 'engraverpass';
CREATE DATABASE engraverdb OWNER engraveruser;
GRANT ALL PRIVILEGES ON DATABASE engraverdb TO engraveruser;

Django settings.py

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': 'engraverdb',
        'USER': 'engraveruser',
        'PASSWORD': 'engraverpass',
        'HOST': '127.0.0.1',
        'PORT': '5432',
    }
}

3. Django Project & App Setup

django-admin startproject engraver_app .
python manage.py startapp inventory

INSTALLED_APPS

INSTALLED_APPS = [
    'corsheaders',
    'rest_framework',
    'inventory',
]

MIDDLEWARE

MIDDLEWARE = [
    'corsheaders.middleware.CorsMiddleware',
]

CORS Allowed Origins

CORS_ALLOWED_ORIGINS = [
    "http://localhost:5173",
]

4. Product Model

from django.db import models

class Product(models.Model):
    name = models.CharField(max_length=100)
    cost = models.DecimalField(max_digits=10, decimal_places=2)
    price = models.DecimalField(max_digits=10, decimal_places=2)
    stock = models.IntegerField()

Migrations

python manage.py makemigrations inventory
python manage.py migrate

5. Django REST API Setup

Serializers

from rest_framework import serializers
from .models import Product

class ProductSerializer(serializers.ModelSerializer):
    class Meta:
        model = Product
        fields = '__all__'

Views

from rest_framework import viewsets
from .models import Product
from .serializers import ProductSerializer

class ProductViewSet(viewsets.ModelViewSet):
    queryset = Product.objects.all()
    serializer_class = ProductSerializer

URLs

from django.urls import path, include
from rest_framework.routers import DefaultRouter
from .views import ProductViewSet

router = DefaultRouter()
router.register(r'products', ProductViewSet)

urlpatterns = [
    path('', include(router.urls)),
]

Project URLs

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/', include('inventory.urls')),
]

Test API

python manage.py runserver

Visit: http://127.0.0.1:8000/api/products/

6. React Frontend Setup with Vite

sudo apt install nodejs npm
npm create vite@latest laser-inventory-frontend -- --template react
cd laser-inventory-frontend
npm install
npm run dev

7. React: Fetch Products & Display Table

import { useEffect, useState } from 'react';
import './App.css';

function App() {
  const [products, setProducts] = useState([]);
  const [newProduct, setNewProduct] = useState({
    name: '',
    cost: '',
    price: '',
    stock: ''
  });

  useEffect(() => {
    fetch('http://127.0.0.1:8000/api/products/')
      .then(response => response.json())
      .then(data => setProducts(data))
      .catch(error => console.error('Error fetching products:', error));
  }, []);

  const handleChange = (e) => {
    const { name, value } = e.target;
    setNewProduct(prevState => ({
      ...prevState,
      [name]: value
    }));
  };

  const handleSubmit = (e) => {
    e.preventDefault();

    fetch('http://127.0.0.1:8000/api/products/', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify(newProduct)
    })
    .then(response => response.json())
    .then(data => {
      setProducts(prevProducts => [...prevProducts, data]);
      setNewProduct({ name: '', cost: '', price: '', stock: '' });
    })
    .catch(error => console.error(error));
  };

  return (
    <div>
      <h1>Laser Inventory</h1>
      <table border="1">
        <thead>
          <tr>
            <th>Name</th>
            <th>Cost</th>
            <th>Price</th>
            <th>Stock</th>
          </tr>
        </thead>
        <tbody>
          {products.map(product => (
            <tr key={product.id}>
              <td>{product.name}</td>
              <td>{product.cost}</td>
              <td>{product.price}</td>
              <td>{product.stock}</td>
            </tr>
          ))}
        </tbody>
      </table>

      <h2>Add New Product</h2>
      <form onSubmit={handleSubmit}>
        <input type="text" name="name" placeholder="Name" value={newProduct.name} onChange={handleChange} required />
        <input type="number" step="0.01" name="cost" placeholder="Cost" value={newProduct.cost} onChange={handleChange} required />
        <input type="number" step="0.01" name="price" placeholder="Price" value={newProduct.price} onChange={handleChange} required />
        <input type="number" name="stock" placeholder="Stock" value={newProduct.stock} onChange={handleChange} required />
        <button type="submit">Add Product</button>
      </form>
    </div>
  );
}

export default App;

Status

  • ✅ Product List (GET)
  • ✅ Add Product (POST)
  • ✅ CORS configured between frontend & backend
  • 🔜 Next: Edit & Delete operations