From enterprise backend to shipping a full‑stack app: the parts I had to learn the hard way
I spent the last four years as a backend developer at one the biggest consulting firm in the world working on portals for large clients. Tight processes, easy/clear requirements, specialist teams. It was good experience. Then I decided to build and ship my own product: a restaurant management system with real users, a frontend, a backend, a database, and a deployment story. That was the moment I realised how many gaps a comfortable enterprise setup can hide.
Where I started
In enterprise:
- Infra shows up when you need it. Databases, SSO, and load balancer? someone else's problem.
- Requirements arrive refined by analysts. Edge‑cases are discussed before you write code.
- Tech choices are largely made for you. You implement within constraints.
When I started this project, there was no safety net. Every unknown was mine to chase down. It was both energising and humbling.
What changed when it was my app
- I had to pick and model the database, then live with those choices when a report needed a different shape.
- I had to implement auth end‑to‑end (JWT), wire it into the UI, and secure the APIs.
- I had to refresh my React knowledge (hadn’t touched it in a while) and build 10+ jsx components.
- I had to package everything in Docker, sort out networking between services, and keep secrets out of images.
None of that sounds hard on paper. In practice, it’s a stream of small decisions with real consequences.
A few mistakes I made (and what fixed them)
- JWT secrets: I started with a too‑short key and hit
WeakKeyException
. Silly right? well I never touched the config server. The fix was simple use a 256‑bit secret but the lesson was bigger: set failure thresholds and alerts for config early. - “localhost” in containers: my app tried to reach Postgres at
localhost:5432
from inside a container. Of course it failed. The fix was using the service name from Docker Compose (db
) and wiring the URL via env vars. - Missing config in the image: I relied on
application-local.yml
existing at runtime. It didn’t in the container. Fix: externalise configuration and pass everything via environment variables. - Duplicate IDs after importing data: I seeded data and later inserted rows from the UI; sequences didn’t match. Fix: reset sequences or use proper migrations.
None of these silly fixes are worthy “achievements”, but time consumed? you already know the answer.
Real‑time without over‑engineering
I wanted live kitchen updates. WebSockets are cool, but I didn’t need “cool” I needed reliable. I started with lean polling backed by efficient queries and clear state transitions (ordered → in‑prep → served). It’s not flashy, but it’s easier to debug. I can always add sockets later when the baseline is rock solid.
Frontend realities for a backend dev
I hit a classic “works on refresh” bug: a role‑gated Admin button wasn’t disabled after login until a full reload. The root cause was state initialisation and when I decoded the JWT. The fix was to centralise auth state (context), derive role once, and subscribe components properly. Boring fix. Worth it.
I also learned that small UX wins (clear errors, obvious disabled states, sensible defaults) save you more time than one more clever abstraction.
What enterprise did prepare me for
- Thinking in layers and seams. That helped me keep controllers thin and services testable across ~30 endpoints.
- Tests. Writing unit tests for the service layer caught regressions when I changed how sessions and orders interacted.
- Security instinct. Default‑deny, explicit roles, don’t trust inputs, even when you wrote the client.
What I’d do differently next time
- Add migrations (Flyway/Liquibase) from day one. Hand‑seeding always comes back to bite.
- Decide env/secret strategy up front and stick to it. Mixing local files and container envs is a trap.
- Budget time for observability. Even basic request logging and a health dashboard change your confidence.
- Ship smaller vertical slices earlier. It’s tempting to perfect the model; it’s better to validate flows end‑to‑end.
If you’re moving from enterprise to “ship it yourself”
- Pick a domain you can observe. I chose restaurants because you can watch the workflow, not guess it.
- Build the simplest thing that proves a flow end‑to‑end, then iterate.
- Prefer boring, reliable choices over impressive ones you can’t support.
- Expect your first week to be 80% plumbing. It pays off.
About the project
- Backend: Spring Boot (Java), JWT, JPA
- Frontend: React (Vite), role‑based dashboards
- Data: PostgreSQL
- Deploy: Docker + Docker Compose
Around 30 endpoints cover authentication, menu management, table sessions, orders (with per‑item status), staff management, and basic analytics.
Code and images:
- GitHub: https://github.com/wl0182/Restaurant-Ordering-System
- Backend image: https://hub.docker.com/r/wassim4592/restaurant_backend
- Frontend image: https://hub.docker.com/r/wassim4592/restaurant_frontend
If you’ve lived in enterprise for a while and you’re curious what’s on the other side: build one real app and ship it. You’ll keep your strengths, and you’ll find the gaps worth closing.
Top comments (0)