Website2026-08-21

Leskovački Kutak — restaurant ordering, delivery and mobile access

A restaurant with two kitchens on two sides of Belgrade that needed a website that knows where the order is going. The customer types in the address, the system selects the nearest kitchen, the order is displayed on the screen in the restaurant without refreshing, and the delivery driver sees it on a phone. In addition, the menu that the owner changes himself and the application that is installed directly from the site.

Homepage of the Leskovački Kutak website
Homepage - Leskovac barbecue with delivery in Belgrade
Client
Leskovački Kutak
Activity
Leskovac grill, restaurant and delivery of food
Area
Belgrade - Veliki Mokri Lug and Leštane
Type
Multi-page site with online ordering and application
Technologies
PHP 8.3 · MariaDB · PWA · Android TWA
Site
leskovackikutak.rs
Status
Live site, under active maintenance
Technology
PHP 8.3 · MariaDB · PWA · Android TWA
01

Problem and context

What does Leskovački Kutak do

Leskovački Kutak is a grill with two locations in Belgrade — Veliki Mokri Lug on Zvezdara and Leštane in Grocka. According to their own story, they have been working since 2018, with the same recipes and the same kitchen in both places.

The menu is classic southern: pljeskavica portions ranging from small to the house Kutak pljeskavica filled with cheese and bacon, kebabs, hanger steaks and pork loin, chicken program, barbecue per kilogram, side dishes, desserts and drinks. A total of nearly ninety items distributed in seven categories.

The business has two streams that do not mix. One is the customer sitting in the bar, the other is delivery — free above the minimum order amount, every day until half past eleven in the evening. And there is the knot of the whole project: the two locations are not one company with two entrances, but two kitchens with their own working hours, their own telephone, their own mailbox and their own part of the city. An order that goes to the wrong kitchen is cold food and a lost customer.

Choosing a location on the Leskovački Kutak website
Choose a location - Veliki Mokri Lug and Leštane, each with its own working hours
02

What needed to be solved

The request was not a website with a PDF menu. They needed a facility that receives orders and takes them to the real kitchen, and the client did not want to pay a commission for each order.

  • The order must know where it is going — the customer does not choose the location from the drop-down list by heart, but the system chooses according to the address to which it is delivered.
  • Address must be correct — a wrongly typed street is a ruined ride and cold food.
  • The kitchen must see the order immediately, without refreshing the page and without waiting for someone to call on the phone.
  • The delivery man works on a phone, not from a piece of paper from the kitchen.
  • Menu changes owner — prices, availability, pictures and attachments, without me and without touching the code.
  • No-book loyalty program — self-calculated and self-spending returns.

In addition to this, what is rarely mentioned in the offer, but must be there: terms of use, privacy policy, consent for cookies that is really respected and a page for job applications, because in the hospitality industry people are always looking for people.

Leskovački Kutak website menu categories
What do you want today — menu categories with the number of items
03

Site structure and order path

The public part has a title, menu, page with locations, about us, credits, contact, gallery, delivery page, FAQ, career and two legal pages. In addition, there are seven thematic pages written for search — Leskovac barbecue, Belgrade barbecue, kebabs, burgers, food delivery, restaurant and family meal.

The menu is divided into seven categories with the number of items written in each, and the filter is kept in the address (?category=), as well as the choice of restaurant (?restoran=). Each item has its own address of the form /product/burger-corner, full image, weight and description — so its own page that can be shared and indexed.

Attachments are not a list stuck on everything. In the database, there is an adjustment by category and especially by individual product, so one is offered with the burger, another with the chicken program, and nothing with the drink. It's a little thing that off-the-shelf solutions usually solve by offering everything with everything.

The basket checks two things before letting the order go: whether the minimum amount for delivery has been reached (for personal pickup there is none) and whether the address is within the range of the selected location. If not, the customer does not receive a silent error, but a clear message and the phone number of that branch.

An account is not a requirement. The customer enters a street, house number and phone number and the order goes through; the account is for credits, saved addresses and order history.

Leskovački Kutak on the phone
Same site on the phone
04

Address, house number and nearest kitchen

This is the part you don't see, and it was worth the most. The address field does not trust the customer to type in the street correctly, but offers it from the register: in the database of the site there are 12,263 streets and 297,101 house numbers, so while typing, the street name is suggested, and then the number that actually exists for that street.

When the address is known, the part of the system in charge of choosing a restaurant calculates the distance to both kitchens and suggests a closer one. If the browser provides coordinates, it is calculated directly from them; if not, the most similar street in the register is searched and its coordinates are taken; if that fails too, the default locale remains. Three steps, but never an empty answer — the order is not interrupted because someone does not allow access to the site.

The practical consequence is simple: a customer from Leštan does not order from Mokrog Lug and wait twice as long, and the restaurant does not drive halfway across town for one burger. The same calculation also serves as a barrier — if the address is out of range, the order is not received silently, but the customer is directed to the branch's phone, where the agreement can be made as before the site.

05

Five applications sharing one database

The site is only one interface within the wider system. It is accompanied by four separate subdomains, each with its own root and its own login: customer app, local panel, delivery panel, administration — and a REST layer through which it all talks to the same database.

There are four roles: customer, local, delivery person and administrator. Each sees exactly her part. The owner of one store does not see the orders of another, delivery staff see only assigned orders, and the administration sees everything.

A new order does not wait for someone to refresh the page. The server pushes it through a permanent connection (Server-Sent Events), so it appears alone on the screen in the kitchen, the delivery driver receives it as soon as it is assigned to him, and the customer sees the status update. For the kitchen, there is also a printout of the order, because the printed ticket travels with the order.

It's all in one MariaDB database with thirty-seven tables — products, categories, add-ons and their settings by category and by product, locations, orders and items, users and addresses, credits with levels and transactions, promo codes, referrals, notifications and delivery zones. One database also means that the turnover report does not have to be compiled from three sources.

06

Credits — a loyalty program that works automatically

The loyalty program is on the page Credits and it is deliberately simple to explain in one sentence: one credit is worth one dinar, credits pay up to half accounts, and credits do not expire.

Return percentage depends on monthly spend and climbs through four tiers — bronze, silver, gold and platinum. The level is not chosen or requested; the system adds up the consumption in the current month, assigns the level itself and records every change in the history, so that later it can be checked why someone got what they got. The same applies to each credit granted and spent — it is recorded as a transaction, not as a manual entry in a spreadsheet.

In addition, there is an invitation for a friend: each account has its own code, the new customer gets a refund on the first orders, and the one who invited him gets his share of the same calculation.

Reason why a refund, not a discount: a discount lowers the price once and that's it, and the credit stays on the account and gives the customer a reason to come back for another order. For a restaurant that lives on repetition, it's a more meaningful move, and it's also easier to explain at the counter.

07

Mobile app

On the page about the application there is a button that installs it directly from the site — without a store and without downloading the installation. It's a progressive web application: a manifest, a service worker, and an icon that sits on the home screen after installation like any other.

The customer gets what they expect from the application: one-touch opening, remembered login, order history, status notifications and working from the cache when the connection breaks in the elevator or in the basement. The app has its own subdomain and its own login screen, so it doesn't interfere with the site.

For Android, a real package was created on top of the same app — a TWA wrapper that opens the same address in the background. To work without an address bar, the subdomain says assetlinks.json in /.well-known/ and confirms that the site and package belong to the same app; without that file, verification fails and the installed app looks like a branded browser tab. The package is signed, and the signing key is stored separately and off the working machine — if lost, later updates are not possible under any circumstances, a new package with a new identity would be required.

The maintenance benefit is straightforward: one app is updated, and the change reaches both on the website and on the phone.

08

Technical performance and places where it broke

Everything is written in PHP 8.3 over the MariaDB base, without WordPress and without plugins to update. The addresses are clean and the server rules translate /product/<slug> into the right script.

Search is a special case, because the menu says "cevapi" and the customer types "cevapi". It was solved in the database: products, categories and locales have generated columns that store the name and description without diacritics and in lowercase letters (č and ć become c, š becomes s, ž becomes z, đ becomes dj), and those columns are indexed. The same functionality exists in both PHP and JavaScript of the application, so search behaves the same on the site, in the application and in the administration — one source of rules, three places of application.

A good part of the job was fixing what was found. All pages of the product were returning 404 because the server lacked a rewrite rule for their addresses, and there were thirty-three links to them from the homepage and from the menu. A bare /delivery from the footer led to the delivery person's application instead of the delivery page. The call button on the front cover was a fictional phone number from the template. Non-existent addresses returned 200 instead of 404, so the search engine treated them as empty pages. The three PHP files were recorded in UTF-16, so PHP did not parse them, and the server served them as plain text — the source code in plain sight.

The worst was IPv6. The domain has an AAAA record, and the server blocks only listened to IPv4, so a visitor going over IPv6 ended up on someone else's site with an invalid certificate. The correction is one line per block, but it is only reached when the site is checked from one side and the other, separately. Mail is also from the same series: verification and contact messages did not go out for twelve days because the sending layer was logging in with an empty password — the configuration where the password is written was not loaded on that path, and nowhere was it seen that something was not working.

09

Registrations that were not human

In August 2026, during the verification of mailboxes on the server, it turned out that the inbox from which the site sends confirmations has about a hundred rejected messages, and rejections arrive that day as well. When looking at the database, the picture was like this: out of 2,256 accounts, 2,230 were created by bots. It lasted from December 2025, and in recent weeks the pace was about thirty registrations per day.

The signature was unambiguous — the name as a series of lowercase letters without spaces, and the email someone else's real address. This means that every such registration sent a "confirm account" message to someone who never asked for it, for eight months straight, from the client's domain. It's not just junk in the database, it's the reputation of the domain from which order confirmations arrive.

The dam is a name rule that is executed before entering the database and before sending mail, and is checked against real data before release: it hits all 2,230 suspicious accounts, none of which ever ordered anything, and it does not touch any real users. The threshold is deliberately set so that a six-letter name passes.

The first dam was not enough. The bots kept coming, because the app has its registration path with its own, weaker verification. A valuable lesson for any account site: before declaring a form protected, find all the places that make a user, not just the obvious. After that, the fake accounts were deleted along with a backup copy of the entire database, and a check showed that no related tables were left with orphans.

It is also worth saying what this number does not mean: an order is not a condition for an order. Most people order as a guest or by phone, so the order number was never even a customer number.

10

SEO, Analytics and Compliance

Each page has its own title, description and canonical URL, and the homepage carries Restaurant and FoodEstablishment data according to the schema.org standard — both addresses, both phones, working hours, delivery area and a link to the menu. This is what the search engine shows when someone searches for barbecue in Belgrade from their phone, and the reason why structured data is written seriously, not as decoration.

The sitemap holds slightly less than eighty URLs and is guided by the rule that only pages that return 200 are in it. When Search Console reported that part of the URLs from the sitemap could not be indexed, the cause was four thematic pages that had since disappeared, plus one redirect and one subdomain. The sitemap was cleaned up, and those pages were later returned as real content — with text that meant something to someone, not empty addresses with keywords.

Analytics is Google Analytics 4 with Consent Mode v2 and default opt-out: until the visitor consents, nothing is sent that doesn't have to. There was also an unpleasant truth. The counter was sitting in a header that was never displayed, because all public pages output their own <head>. For months it looked like the measurement was working, but it wasn't measuring anything. It was solved with one common file included in all sixteen templates that output the document head, so the order is now the same everywhere: consent first, then measurement.

A cookie banner also offers rejection, not just acceptance, the choice is remembered and respected on the next visit.

11

Outcome

What the client received

Own order channel. No commission per order and no subscription to the platform — the customer, his address and his history remain in a database that belongs to the client and can be copied and moved.

The day looks like this: a new item is added through the administration and immediately it is on the menu; the customer types in the address and the system determines which kitchen he is going from; the order appears on the screen in the bar and can be printed immediately; the delivery driver sees it on a phone; credit rewards are calculated without anyone's intervention.

The site is still under active maintenance, and of everything I've done, this project is the best reminder that work doesn't stop at launch. None of the problems described above were apparent at first glance: the site looked fine both when sending mail with an empty password, and when all product pages returned a 404, and when it was showing someone else's site over IPv6 to a visitor. It was only seen when it was checked piece by piece, across both the public site and its internal applications.

The entire ecosystem, with all panels and recordings from the production, is also shown on the special page about this application.

FAQ

Frequently asked questions

Why order yourself when there are delivery platforms?

Because there is no commission per order on its own website, and the customer and his address remain in the restaurant's database. Platforms bring reach and make sense as a supplement, but a channel that is yours cannot raise your commission tomorrow or drop you in its list.

Can the owner himself change the menu and prices?

Yes. Products, categories, prices, images, availability and attachments go through the administration, without touching the code. The same applies to locations, opening hours and credit program settings.

What if the restaurant has more than two locations?

The system was written from the beginning for multiple locations, not for one with an exception. A new restaurant is added with its address and coordinates and immediately enters the calculation that selects the nearest kitchen for the given delivery address, with its working hours and its panel.

Does the customer need an account to order?

No. The customer enters a street, house number and phone number and the order goes through. The account is used for credits, saved addresses and order history — it's a convenience, not a requirement.

How does an order reach the kitchen?

The server pushes it to the bar's screen through a permanent connection, so it appears automatically, without refreshing the page. There is also a printout of the order for the kitchen, and the delivery person receives his order on his phone as soon as it is assigned to him.

Is the app downloaded from an app store?

It does not have to. It is installed with one button from the site and then it is on the home screen like any other application. For Android, a package with Digital Asset Links verification was created and signed for the same app, so publication in the store remains an open option without rewriting the application.

Next step

Have a project that needs to work better?

Send me your goal and current situation. I will reply with a concrete scope proposal.

Start a project