| Let the backend send what's convenient to them. In the frontend, create Models that transform to what's convenient to the FE. Sometimes, the FE needs two models for the same data. For example, a Table View and a Nested Card View of the same collection. Model Example: const UF = { // User Fields
id: 'user_id',
name: 'name',
balance: 'remaining_dollars',
balanceFormatted: 'balanceFormatted' // @ClientSideOnly
}
function UserModel(data) {
const model = Object.create(null)
Object.assign(model, data)
model[UF.id] ??= ''
model[UF.id] = String(model[UF.id])
model[UF.name] ??= ''
model[UF.balance] ?? = 0
model[UF.balanceFormatted] = toDollars(model[UF.balance])
return model
}
In that example:1. You are protecting from prototype pollution. `model` doesn't have a `__proto__` 2. Initialized the fields with sane defaults. So if the backend missed that field, it doesn't crash the UI. By the same token, they are handy for prototyping while the backend is written. 3. Your views a free of formatting, and in some cases of many conditionals. 4. You don't have to worry about int ids. Not because they are often changed to GUIDs, but because when you use the ID, let's say in a checkbox, `this.value` is a string regardless. So you saved 30 minutes of debugging a silly bug. 5. Backend wants to rename a field. You just have to rename the UF, without having to look all over the app. 6. The model is easy and fast to unit test, while the testing the UI is… 7. You saved 3 meetings. -- My personal project is data-driven and even there I pass the backend data through model constructors. One example is that in the frontend I use a Linked-List for convenience, while the backend sends and receives an Array. |