Overview
CyberWorks is an online store for computer components, peripherals and pre-built systems for the Bulgarian market. The project has three goals:
- a complete online shopping experience, from product search to a finished order;
- automatic updating of the assortment from a distributor, with no manual data entry;
- a tool that lets a buyer with no technical knowledge assemble a compatible computer on their own.
The platform covers the whole process: catalog, search, product pages, accounts, cart, favorites, checkout with courier delivery, email notifications and an interactive PC configurator.
The challenge
The project had several core difficulties:
- A large, constantly changing assortment. The distributor offers about 5,800 products, of which about 2,400 are in stock at any given time. Prices and availability change every day, so manual maintenance isn’t possible.
- Heterogeneous and incomplete data. The supplier data is in English, the categories don’t match the store’s, and the specifications are unstructured text formatted differently by each manufacturer. Important values are often missing — for example graphics card length or cooler height.
- Component compatibility. A buyer must be able to build a computer without knowing what a socket, DDR4/DDR5 or form factor is, and the system must not allow an incompatible configuration.
- Hosting limits. The site runs on shared hosting with limited memory and a PHP environment. The solution had to work reliably under those conditions.
- Security. Content, images and HTML descriptions come from an external source and must be treated as untrusted.
Architecture
The system is split into three layers:
- Frontend: a React SPA with React Router and Vite. Global state (user, cart, favorites) is managed with React Context.
- Backend: a REST API in two versions with identical logic. Node.js/Express is used for development and PHP for the production hosting. All endpoints for search, categories, product details, cart, favorites, users and orders exist in both versions.
- Data: MariaDB/MySQL with a separate table for the distributor’s products (
products_most) that takes priority over the legacy products table. Search, categories and product pages check the new table first and then the old one. This made the transition between the two data sources seamless, with no downtime.
Key features
PC configurator with a compatibility engine
This is the most complex feature of the project. The configurator covers eight component types: CPU, motherboard, RAM, graphics card, storage, power supply, case and cooling.
Order doesn’t matter. The user can start from any component. Every candidate is checked against all parts already selected, not just the previous one, so the lists are filtered correctly in whatever order parts are chosen.
The check has two levels:
- Hard rules. Parts that can’t work together aren’t shown in the list at all, so every visible option is compatible. The rules are:
- CPU socket vs. motherboard socket;
- RAM type vs. motherboard and vs. CPU (so RAM and CPU constrain each other even before a motherboard is chosen);
- motherboard form factor vs. case;
- graphics card length vs. space in the case;
- cooler compatibility with the CPU socket;
- power supply wattage vs. the configuration’s real power draw.
- Soft warnings. They don’t block the choice, they only flag possible issues (⚠):
- a cooler that may be too tall for the case;
- a motherboard that may not have a free M.2 or SATA slot;
- a power supply within 10% of the required wattage;
- a CPU without integrated graphics and no graphics card selected.
Power calculation. Consumption is calculated from the CPU TDP, the GPU TDP and a base system draw. The recommended power supply has about 40% headroom and is rounded up to the nearest 50 W. When the graphics card manufacturer recommends a stronger PSU, the higher value is used.
Handling incomplete data. If a specification can’t be parsed, it is treated as “unknown” and never blocks a match. A part is excluded only when both values are known and contradict each other. Without this rule, the supplier’s incomplete data would empty out the option lists.
Performance. The configurator specs are extracted once, during the import, and stored as JSON in the database. The server returns all available components grouped by type, and filtering happens in the browser. The UI reacts instantly, with no new request on every selection.
Automatic product import
Every day the system downloads the distributor’s full XML feed and processes the products in several steps:
- Category mapping. The supplier’s English categories and subcategories are mapped to the store’s Bulgarian structure.
- Translation and formatting. Specification headings are translated into Bulgarian and the description is rendered as an HTML table.
- Pricing. A pricing rule is applied on top of the supplier price, with prices in euro.
- Availability. Only products with status “In stock” are marked as available. Products with status “Call us” and those no longer in the feed are hidden automatically.
- Images and identifiers. The first image becomes the main one and the rest form the gallery. EAN, part number, warranty and promotion status are stored.
- SEO URLs. URLs are generated with the same algorithm the frontend uses, so links always match.
- Configurator classification. Products that are components get a type and structured specs: socket, memory type, form factor, TDP, wattage, dimensions and more.
- Writes without duplicates. Existing records are updated and new ones are added.
Two versions. The first version is a Node.js script run from Windows Task Scheduler that writes to the database over VPN. For more reliability, a PHP version was later built that runs from cron directly on the hosting. It reads the XML file in chunks with XMLReader to stay within the shared hosting’s memory limit. Both scripts have a dry-run mode that processes the feed without writing anything to the database.
Catalog, search and product pages
- A catalog with categories and subcategories generated dynamically from the products in stock.
- Search by name and description, sorting by price and filtering by category.
- Product pages with a gallery, a specs table, warranty and availability.
- SEO URLs for every product, resolved to an ID on the server.
Users, cart and orders
- Sign-up and login with JWT, with the token stored in an httpOnly cookie.
- The cart and favorites are stored in the user’s account.
- Pre-built PC configurations that go into the cart as a single product.
- Checkout with Econt office selection. The list of offices is loaded from the courier’s service.
- Automatic order confirmation email via PHPMailer.
- A contact form protected with reCAPTCHA.
- Price list, FAQ, terms and conditions and privacy policy pages.
Security
The distributor’s data comes from an external source and is untrusted, so protection works on several levels:
- Image proxy. The browser loads images only from the store’s own domain. The proxy looks up the real URL in the database by product ID and never takes it from a request parameter. This prevents the proxy from being abused for an attack (SSRF), and it only fetches images from approved hosts. On error it returns an empty image.
- Content sanitization. HTML descriptions are sanitized and image URLs are checked against an allow-list of hosts. URLs such as
javascript:ordata:are rejected. - Authentication. Passwords are hashed with bcrypt, and JWT tokens have a limited lifetime and are stored in httpOnly cookies that JavaScript can’t access.
- CORS. API access is restricted to explicitly allowed domains.
Technical decisions and trade-offs
- Two backends with identical logic allowed comfortable development on Node.js and running on standard PHP hosting. The cost is that every change to the import, pricing or specs logic has to be made in two places.
- Pre-computed specifications moved the heavy text processing from request time to the nightly import.
- In-browser filtering in the configurator gives instant feedback without extra server load.
- The “unknown never blocks” rule was a deliberate choice between strictness and usefulness with incomplete data. Reliable data (socket, memory type) is checked strictly, while less reliable data (cooler height) only produces a warning.
- Two product tables with priority for the new one allowed a gradual migration to the new supplier without downtime.
Result
The result is a working online store (cyberworks.bg) with a fully automatically maintained catalog of thousands of products. The PC configurator protects customers from incompatible purchases, and the infrastructure runs reliably on shared hosting. The site is mobile-friendly, has Framer Motion animations, social sharing previews (Open Graph, Twitter Cards) and behavior analytics with Microsoft Clarity and Meta Pixel.


