I don’t know how I went this long without hearing the term DTO.
It stands for Data Transfer Object, and somehow I had never encountered it.
The idea sounds almost too obvious in hindsight: when data moves between layers say, from your controller to the frontend you don’t
just hand over your full model. You wrap it in a structure designed for that exact transfer.
At first, I lowkey thought DTOs were about making things neat. But then I realized they’re also about also mking things efficent like by aggregating multiple pieces of related
data so the client doesn’t have to make several expensive round trips ,to the server. And, importantly, they’re about exposure control: sending only what’s needed, nothing more.
For my Room entity, I made four:
I made four for my room entity
RoomCreateRequest
JoinRequest
RoomResponse
RoomUpdateRequest
I initially wanted to store a List<%User> directly inside each Room. It felt intuitive. But MongoDB’s 16MB document limit and the risk of tight coupling (duplicating user info in every room they belong to) convinced me otherwise. If a user changed their name, I’d have to update that data in multiple places — a maintenance nightmare. Switching to userId references made the model looser, more maintainable, and aligned with single source of truth principles.
Global Exception Handling
In the early days, when something went wrong missing ID, invalid field, duplicate entry I just threw raw exceptions.
Technically it worked, but it was ugly. Debugging was just pasting crash logs into ChatGPT just to make sense of them.
DTOs and Service Layer
So far , we have come from Zero to a room entity , Well almost the same process for user entity , but i will write that as well
Once I had the DTOs in place, the next step was to give them a job.
Well technically, I had written them because I already had a job for them. Drumroll please... yes: Data Transfer.
Shocking, I know.
The RoomService acts as the orchestration layer, enforcing business rules and coordinating repository calls for all room-related actions (create, edit, join, lookup).
Controllers handle routing, directing HTTP requests to the right service methods.
Postman tests covered:
Creating a room
Joining with valid/invalid invite codes
Preventing duplicate joins
Updating a room name
All behaviors worked, returning proper HTTP codes (404 for not found, 409 for duplicate join).
Unit tests aren’t in place yet — manual testing is used until authentication is implemented.
“sErViCe lAyEr iS wHeRe yOu wRiTe bUsiNeSs rUlEs”
— This guy on YouTube
Well, yes. This is where the actual decisions live. The RoomService became the part of the codebase that does things.
Want to create a room? Edit one? Join one? Get the details of one?
That’s all here.
Controllers
Controllers - you know , Air Traffic ..... yes , Air Traffic Controllers help direct planes and tell them what runway or where to go generally
These guys:
Well here too , Our controller , or more specifically , RoomController , tells any request (associated with room) what runway to land
You want to create a room? Take runway /api/rooms
“Trying to join with an invite code? Proceed to /api/rooms/join.”
“Just want to look up a room’s info? That’s gate /api/rooms/{id}.”
I exposed routes
Testing
Did I test these routes? Yes. I used Postman.
->Created a room
->Joined using a valid invite code
->Tried an invalid code
->Tried joining the same room twice
->Updated the room name
All the expected behaviors worked. Error handling also returned the correct status codes (404 for not found, 409 for duplicate join).
Did I write unit tests?
No. I don’t know how to write them yet.
That’s something I plan to learn after authentication is done. Right now, I’m relying on manual testing to validate logic.
See you on Day 2