Back to Projects
Restro
CompletedGolangGo TemplatesMySQL+1 more

Restro

A Go and MySQL restaurant app: server-rendered menus, browser-side carts, table allocation and an explicit order lifecycle.

Timeline

Ongoing

Role

Backend Developer

Team

Solo

Status
Completed

Technology Stack

Golang
Go Templates
MySQL
Docker

Key Challenges

  • Keeping table allocation and order writes consistent
  • Separating authentication from permission checks
  • Defining which order status changes are allowed

Key Learnings

  • Reusing parsed Go templates across requests
  • Modelling orders and line items in MySQL
  • Finding transaction boundaries in a multi-step write

Restro handles a restaurant workflow: a customer chooses items, submits an order and gets a table number. Staff move that order through cooking, service and billing. The implementation uses Go, Gorilla Mux, html/template and MySQL, with JavaScript for the cart and status buttons.

Rendering pages without a frontend framework

At startup, cmd/main.go loads configuration, parses the templates, initializes sessions and connects to the database before starting the router. The template loader collects page templates, admin pages, partials and components into one template.Template. Handlers reuse it through ExecuteTemplate.

GET /menu reads category and search parameters, fetches items and renders menu.html. Search replaces the category result when a search term is present; it does not combine both filters. The response includes the menu data, so the browser does not need another request to populate the page.

The cart stays in localStorage. JavaScript handles quantity changes and extra instructions, then sends item IDs and quantities to POST /order. The server looks up prices in MySQL to calculate the total. A price shown in the browser is therefore not the value used to create the order.

After submission, the handler stores the order ID and table ID in a Gorilla session and returns a URL for the browser to navigate to. This mixes ordinary HTML navigation with small JSON endpoints where an interaction needs them.

One order, several database writes

The schema separates orders from order_item. An order stores its customer, table, instructions, status, total and timestamp. Each line item stores an item ID and quantity, with a composite primary key on the item and order IDs.

AddOrder reads available tables, chooses the first result and marks it unavailable. It then inserts the order and its line items. These are separate database calls, without a transaction around the sequence.

That leaves two concrete failure cases. Concurrent requests can select the same table before either marks it occupied. A failed insert can also leave a table occupied without a complete order. Go can serve requests concurrently, but that does not make this sequence atomic.

A change I would make here is to reserve the table and write the order inside a transaction, using a locking read or a conditional update to prevent two requests from claiming it. Line-item insert failures would then roll back the order and the reservation together. This is a proposed change, not behavior the current implementation provides.

The order lifecycle has a point of no return

Orders start as placed. The transition validator allows changes between placed, cooking and served, including moving backwards. Those states can also move to billed. Once billed, an order can only become paid; paid orders cannot change state again.

That distinction supports correcting a preparation status while keeping the billing boundary explicit. The PATCH handlers check that the order exists, read its current status and validate the requested transition before writing. Marking an order paid also makes its table available again.

The validation and update are separate queries, though. Two requests can both validate an old status before either writes. A conditional update against the expected status would let the database detect that conflict.

The chef template displays only placed and cooking orders. The admin order view can filter by status, but its renderer still fetches the full matching set. There are pagination hooks in the template; database pagination is unfinished.

Authentication and staff permissions

Passwords are hashed with bcrypt at cost 10. Login issues an HS256 JWT in a token cookie. Protected routes validate it, check that the user still exists and put the token's user data in the request context.

The Go role constants are customer, admin and super. Registration makes the first user a super user. Although the SQL enum includes chef, the chef page is registered under /admin, rather than behind a separate chef role.

The routing also exposes an implementation gap: order status endpoints sit on the authenticated /order router, without the admin middleware. The admin middleware redirects a non-admin but does not return before calling the next handler. Those checks need to be fixed before describing the app as having complete role-based enforcement.

Running it and measuring it

Docker Compose starts MySQL and the Go application. It waits for the database health check before starting the app; the entrypoint applies SQL migrations with golang-migrate and then executes the server. The database connection pool allows 25 open connections and five idle connections, with a five-minute maximum connection lifetime.

The repository includes ApacheBench scripts for registration and the admin order page, configured for 1,000 requests at concurrency 10. They give me a starting workload to investigate. They do not establish a speedup over Express: there is no equivalent Express implementation or comparison harness in the repository.

For the next pass, I would measure latency and failed requests while checking that every successful order has its line items and a valid table reservation. A quick response is useful only if the order it represents is complete.

amxcodes

Software developer building backend, web and AI agent systems.

© 2026 amxcodes. All rights reserved.