One thing I'd add to this architecture: be very careful with Socket.io's default acknowledgement timeout behavior under packet loss. In our project we were seeing desyncs on mobile clients with spotty connections because dropped events weren't being retried — they just silently failed.
We ended up implementing a lightweight sequence-number system on top of Socket.io: every authoritative state update gets a monotonic seq field, clients track the last confirmed seq, and on reconnect they send that seq so the server can replay missed deltas. It's more work than it sounds but saved us from a whole class of ghost-state bugs. Also worth looking at uWebSockets.js if you hit throughput limits — we saw roughly 3x the connection capacity versus Socket.io for the same hardware once player counts got into the hundreds.
