Durable Object foundations
Route deterministically to an instance
Use getByName for stable entity routing and keep authorization outside the fact that an object name can be guessed.
A Durable Object namespace is a map from a name to one instance. Give it the same name twice and you reach the same object. Everything else builds on that property.
The shortest way to get a stub is getByName:
const room = env.ROOMS.getByName(roomId)
await room.addMessage(userId, body)
Under the hood this does two steps you can also write by hand.
Derive the object ID from the entity that owns coordination:
const id = env.ROOMS.idFromName(roomId)
const room = env.ROOMS.get(id)
return room.fetch(request)
The same roomId reaches the same logical object. Do not include a random request ID in this name or every request creates a different coordination boundary. Test two users in one room and one user in another room to prove isolation.
Both forms are fine. getByName is shorter, and inside the object this.ctx.id.name gives you the name back. That’s handy in an alarm handler, where no caller passes it in.
A name is not a password
Here’s the mistake I see most. The room ID comes from the URL, the Worker calls getByName(roomId), and whoever guesses a room ID is in.
The name is routing information, nothing more. It tells the platform where to send the request. It says nothing about whether this caller may send it.
So verify the caller first, either in the Worker before creating the stub or inside the RPC method:
const member = await isMember(env.DB, roomId, session.userId)
if (!member) return new Response('Forbidden', { status: 403 })
const room = env.ROOMS.getByName(roomId)
Creating a stub does not wake the object, so the check costs nothing extra. The object only activates when you call a method on it.
When you don’t have a name
newUniqueId() creates a random ID. Use it only when you have somewhere durable to store the mapping, like a D1 row that says “document 812 lives at object a1c9...”. Lose the mapping and the object is unreachable forever.
When the entity already has a stable identifier, derive the name from it and skip the mapping table.
Try this: route two test room names, standup and design, and call each twice. The second call to standup must see the first call’s message. design must see nothing. That’s your proof that state is shared within a room and isolated between rooms.
Lesson completed