- Own and evolve the platform API gateway: request routing and proxying to the backend services behind it, JWT and API-key authentication middleware, and a Socket.IO real-time layer scaled horizontally with a Redis adapter.
- Shipped an end-to-end API-key management feature spanning four services — issue, list, revoke, delete — giving customers secure programmatic access to live device data with full audit logging.
- Built event-driven services processing 56 million+ daily GPS events over RabbitMQ pub/sub and WebSocket pipelines, cutting device-event delivery latency by 40%.
- Designed a queue-based report worker on BullMQ and Redis that renders Excel and PDF artefacts, uploads them to S3 and emails a link — keeping heavy work off user-facing APIs.
- Co-architected the migration of a legacy monolith into decoupled services, accelerating deployments by 50%, applying domain-driven design to keep the seams maintainable.
- Hardened inter-service messaging with RabbitMQ connection-recovery logic and Redis failure handling after diagnosing critical production anomalies.
- Built a scheduled S3 cold-storage archiver for historical telemetry, slowing hot-database growth and query cost.
- Wrote the nightly MongoDB backup job the platform runs on: two dump cadences in one cron entry, atomic archive writes, offsite copies to S3, and retention pruned on the date in the filename rather than on mtime.
I build backends from the wire up — binary device protocols at one end, API contracts at the other, and fifty-six million events a day moving between them.
Software Engineer II on a fleet-tracking platform, working across service boundaries rather than inside one. I own the API gateway, the real-time layer and the event pipelines that hold a fifteen-service Docker Swarm stack together — device management, alarms, notifications, reporting.
Most of what goes wrong in a distributed system goes wrong at a boundary: two services disagreeing on a contract, a publisher that never reconnects after the broker restarts, a badge count that quietly scans a collection. Those are the parts I like.
The wire end of that is the unusual part for a web backend engineer: Teltonika Codec 8, 8E and 12 and Gator M528 frames parsed off the socket, cold-chain temperature records under EN 12830 where the correctness bar is legal rather than aesthetic, and a self-hosted Valhalla routing service, with tiles built from OpenStreetMap extracts, that I set up and that now runs in our production cluster.
- Years shipping backend
- 4Years shipping backend
- Services in the production stack
- 15Services in the production stack
- GPS events ingested daily
- 56M+GPS events ingested daily
- Services one feature spanned
- 4Services one feature spanned
⌘K or / opens a jump list, if you would rather not scroll.
Career
Two roles and a degree, on one axis. Click a bar to read the detail.
The overlap is real: the last two years of the degree ran alongside client work, and Bawq started a month before graduation.
- Partnered with clients and stakeholders to turn vague requirements into backend architectures they could actually operate.
- Delivered production REST and GraphQL APIs with system documentation written for the people inheriting them.
- Improved database and application performance by up to 35% through Redis caching and query tuning.
- Coursework in data structures, operating systems, databases, distributed architectures, computer architecture and programming languages.
- Built an information-retrieval engine from a spec — positional index, TF-IDF weighting, cosine similarity — rather than importing one.
The work, measured
Three results and three measures of scale, each attributed to the role it came from.
- device-event latency cut
- 40%
- Bawq Telecom
- faster deployments after the split
- 50%
- Bawq Telecom
- database & app performance gained
- 35%
- Freelance
- GPS events ingested per day
- 56M+
- Bawq Telecom
- services in the Docker Swarm stack
- 15
- Bawq Telecom
- alarm types on live telemetry
- 9
- Bawq Telecom
Selected work
Told as architecture — the code is in a private repository.
Fleet customers want speeding reports. But "speeding" means nothing without the legal limit on the road the vehicle was actually on, which means resolving a limit for every point in a continuous telemetry stream. Commercial roads APIs bill per lookup, and that model does not survive contact with thousands of devices reporting all day.
So we stopped buying lookups. I set up Valhalla, the open-source routing engine, built its routing tiles from an OpenStreetMap extract of the operating region, and queried it for the speed limit on the road segment each point matched to. It did not stay a prototype: it runs today as its own service in our Docker Swarm stack, next to the services that call it. Readings are enriched at ingest time, so a report reads a stored number instead of making a network call per row.
The interesting part is the blast radius. It touched ingest-time enrichment in device management, a shared internal package for the lookup, contract validation at the gateway, new report types and scheduling fields in reporting, and the column layout in the worker that renders the file — including making an estimated limit visually distinct from a posted one, because a compliance-minded customer will absolutely ask which is which. It shipped, broke on a misconfigured routing endpoint and an under-provisioned instance, got reverted across every service rather than patched forward, and went back out four days later once timing instrumentation found the real cause.
Would revisit
Enrichment at ingest is cheap to read and expensive to correct. Re-deriving limits for historical rows after an OSM update is still a batch job I would rather not run.
Fleet reports cover long date ranges over high-volume telemetry. Generate one inside a request and it times out. Generate several at once and the API stops answering anyone.
The design is a straight producer/consumer split. The reporting service owns report definitions, timezone-aware cron scheduling and enqueueing; a dedicated worker consumes a BullMQ queue, renders the artefact — xlsx via exceljs, PDF via pdf-lib — uploads it to S3 and emails a link. The request returns immediately; the file arrives when it is ready.
Two things that sound like details and are not. The queue is also the load-levelling mechanism — a customer scheduling twelve monthly reports for the first of the month is a spike you want absorbed and worked off at the pace the worker sets, not served all at once. And every report type needs its own column configuration, because a temperature report and a speeding report share almost nothing except the vehicle it happened to.
Would revisit
An emailed S3 link is simple, and it is also a support ticket every time a link expires. Streaming from an authenticated download endpoint would have been the kinder choice.
Nine configurable alarm types — parking, idling, engine, speed, temperature, seatbelt, geofence duration, geofence speed, panic — each evaluated against a live telemetry stream, each respecting a per-device activation schedule, each routed over RabbitMQ to the notification service for delivery by push, email or SMS.
Detection was never the hard part. Noise was. An alarm engine that fires correctly but constantly is worse than no alarm engine, because operators stop reading the alerts and then miss the one that mattered. Most of the real work went into activation-time validation, duty-hour windows and threshold tuning — deciding that a temperature alarm on a vehicle parked and unplugged overnight is not an event.
Turf.js supplies the geometry — is this point inside that polygon — while entry, exit and dwell are state layered on top of it. I built that logic out as standalone spikes before it went near the service, which is unusual for me and was correct here.
Would revisit
Thresholds live in configuration, which makes tuning them a deploy-adjacent activity. They belong with the operations team, alongside a preview of how many alerts a change would have produced last week.
A nightly mongodump of the whole database is the obvious thing to write and the wrong thing to run. The volume was lopsided: the bulk telemetry was almost all of the database on disk, while the records you actually need to bring a customer back were a small fraction of it — and the expensive half barely changed shape from one night to the next.
So the job runs two cadences out of one cron entry. The operational data is dumped nightly; the bulk telemetry is dumped on a much longer cycle, and only when the archive for that period is missing, which makes it safe to re-run — running it twice in a day overwrites the nightly archive and skips the expensive one. Each archive is gzipped on the way out of mongodump, written to a .part file, and renamed only after the dump exits clean and the file is non-empty. An interrupted run leaves no artefact that could be mistaken for a good backup. The finished archive is copied to S3, and the retention window is enforced in both places rather than only on the box.
The detail I would defend in review is how it prunes: on the date parsed out of the filename, never on mtime. Modification time is not a property of a backup — a plain cp resets it, so does rsync without -t, so does restoring from another tool — so a pruner that trusts it either keeps archives forever or deletes a fresh one, and you find out which during an incident. Beyond that it is unglamorous on purpose: POSIX sh rather than bash, so it does not depend on bash being installed (it does lean on GNU date for the cut-off arithmetic), set -eu so any failed step stops the run, and logging to both stdout and a file, so whatever collects cron output sees each run while the full history stays on disk.
Would revisit
A backup nobody has restored is a hypothesis. This should end by restoring the newest archive into a throwaway container and asserting a few record counts — otherwise the first real test of the whole thing is the morning you need it.
JWTs are stateless. That is the feature, and it is exactly why revoking one is awkward: a signed token stays valid until it expires whether or not you still want it to be.
I built session management on Redis-backed session caching, which buys revocation at the cost of reintroducing the state JWTs were chosen to avoid. That trade is the whole design decision, and I would make it again: every request now pays a Redis lookup. The alternatives are short-lived access tokens with refresh, which leaves a revoked token usable until it expires, or a blocklist, which is a session store wearing a hat. What it enabled: revoke one session, revoke all sessions except the current one, list active sessions per user, and an administrative path to clear every cached session for an account.
Around it sits the full user lifecycle — an admin invites a user, an invitation email goes out, the user sets a password, gets auto-logged-in and verified, and has 2FA enforced if their organisation requires it. Deactivation blocks login without deleting the account, and deleted users are soft-deleted so they disappear from queries without breaking every historical record that referenced them.
Would revisit
Enforcing 2FA org-wide without locking out the people already logged in is a migration, not a setting. I would design the grace period first next time.
Stack, by how well I know it
Grouped by evidence rather than enthusiasm. Pick a level to narrow it.
Languages
Backend & APIs
Messaging & jobs
Data & search
Cloud & ops
Geospatial & devices
Testing & quality
Frontend
Amber is daily production use. The fainter a chip is, the less I would claim about it — the bottom tier means I can read it, not that I know it.
MAZ Realty
·FreelanceA real-estate product that actually shipped
Real-time chat and property-management workflows on a scalable Node.js backend, with end-to-end Stripe payments. Including a non-obvious piece: booking a lawyer for document verification. The interesting problems in property listings are rarely the listings.
Node.js · TypeScript · MongoDB · Redis · Socket.IO · React · Stripe
gh-visualizations
·PersonalThe best-engineered thing I own publicly
Raw SQL with numbered migrations, Zod-validated config, lint-enforced conventions, husky gates, and its standards written down rather than assumed. Also: no AI-generated code in it.
TypeScript · SQLite · Express · Zod · Jest
ticketing
·PersonalThree services and a Next.js client on an event bus, orchestrated on Kubernetes
Optimistic concurrency control across services communicating over NATS Streaming, a published shared npm package, and test-driven development throughout. It started as a course build — worth saying so — but it is the distributed patterns I work with daily, in code I am allowed to show.
TypeScript · Next.js · NATS Streaming · Kubernetes · Skaffold · Jest
logs-monitoring
·PersonalCentralised logging for a microservice fleet
Producer services publish logs to RabbitMQ; a consumer indexes them into Elasticsearch for querying and monitoring. The small version of a problem every distributed system eventually has.
TypeScript · Node.js · RabbitMQ · Elasticsearch · Docker
rssagg
·PersonalAn RSS aggregator in Go
chi, sqlc, goose, Postgres and a background scraper. Evidence that Go is something I have used rather than something I have listed.
Go · chi · sqlc · PostgreSQL
Information-Retrieval-System
·UniversityA search index built from the spec, not the library
A positional index with phrase queries, TF-IDF weighting and cosine-similarity ranking, built from a specification and finished. Only the stemmer is a library; I like implementing the algorithm occasionally instead of importing it.
TypeScript · Express
GitHub, right now
Fetched when you opened this page — profile and activity from the GitHub API, the calendar from a public mirror of it.
- Contributions / yr
- —Contributions / yr
- Public repos
- —Public repos
- Followers
- —Followers
- On GitHub since
- —On GitHub since
Loading the last year…
Writing
Eighteen posts, mostly because explaining a thing is the fastest way to find out I don't understand it yet.
- AnsibleOps
- Caching with RedisData
- API Performance TestingBackend
- Advanced Node.jsBackend
- Auth using OTPSecurity
- My bash settingsOps
Certifications & teaching
- Software Architecture FoundationsLinkedIn Learning
- MEAN Stack DevelopmentInformation Technology Institute (ITI)
- Web Development InstructorDigital Egypt Cubs Initiative (DECI)
- Explore Web Development with Node.jsLinkedIn Learning
What I'm working on next
The two gaps I'd rather name than have you find.
Testing
The platform I work on has thin coverage. Where a service was new enough to allow it I added a proper suite — in-memory MongoDB, integration tests, the works — but retrofitting the older ones has not been mine to fix. So I am fixing it where it is: a genuine suite in my own public repositories, starting with the one I would want a reviewer to open.
Observability
I have leaned on ad-hoc logging far more than on structured metrics or tracing. It is the most common gap at my level and worth closing before someone asks me to debug a latency spike out loud.
Get in touch
Cairo, Egypt
Open to backend roles — distributed services, event pipelines, the parts that have to stay up. Tell me what you are building and what is hard about it, and you will get a real answer back.