Data Modeling and State Management
Understanding state, entities, normalization, serialization, and data modeling patterns in JavaScript applications
Introduction
How you model and manage data determines the architecture of your entire application. Understanding state, entities, normalization, and serialization helps you build applications that are predictable, performant, and maintainable.
What is State?
State is data that changes over time and determines what your application displays and how it behaves.
// Application state examples
const appState = {
// UI state
theme: 'dark',
sidebarOpen: false,
activeModal: null,
// Domain state
currentUser: { id: 1, name: 'Alice' },
cart: { items: [], total: 0 },
// Server state (cached)
users: [],
posts: [],
// Communication state
loadingStates: {},
errors: {}
};
Types of state:
- UI state — Modals, themes, sidebar visibility
- Domain state — Business data (users, orders, products)
- Server state — Cached API responses
- Communication state — Loading flags, error messages
Entities
Entities are objects with a unique identity that persists over time:
// Entity definition
interface User {
id: string; // Unique identifier
name: string;
email: string;
createdAt: Date;
updatedAt: Date;
}
// Entity identity
const user1 = { id: '1', name: 'Alice' };
const user2 = { id: '1', name: 'Alice Updated' };
// Same entity (same id), different state
const user3 = { id: '2', name: 'Alice' };
// Different entity (different id), even though name matches
Entity characteristics:
- Has a unique identifier
- Mutable over time (state changes, identity stays)
- Can be compared by ID, not by value
- Often corresponds to database rows
Normalization
Store data in a flat, relational structure rather than nested:
// ❌ Nested — hard to update, duplicates data
const nestedState = {
posts: [
{
id: 1,
title: 'Post 1',
author: { id: 1, name: 'Alice', avatar: 'alice.jpg' },
comments: [
{ id: 1, text: 'Great!', author: { id: 2, name: 'Bob' } }
]
}
]
};
// ✅ Normalized — single source of truth, easy updates
const normalizedState = {
posts: {
byId: {
1: { id: 1, title: 'Post 1', authorId: 1, commentIds: [1] }
},
allIds: [1]
},
users: {
byId: {
1: { id: 1, name: 'Alice', avatar: 'alice.jpg' },
2: { id: 2, name: 'Bob', avatar: 'bob.jpg' }
},
allIds: [1, 2]
},
comments: {
byId: {
1: { id: 1, text: 'Great!', authorId: 2, postId: 1 }
},
allIds: [1]
}
};
Benefits of normalization:
- ✅ Single source of truth — update a user once, reflected everywhere
- ✅ No data duplication
- ✅ Easier updates — modify flat objects
- ✅ Better performance — avoid deep cloning
Serialization
Converting data between in-memory objects and transferable formats:
// Serialize: object → JSON string
const user = { id: 1, name: 'Alice', createdAt: new Date() };
const json = JSON.stringify(user);
// '{"id":1,"name":"Alice","createdAt":"2024-01-01T00:00:00.000Z"}'
// Deserialize: JSON string → object
const parsed = JSON.parse(json);
// Note: createdAt is now a string, not a Date!
// Custom serialization with toJSON()
class User {
constructor(id, name) {
this.id = id;
this.name = name;
this.createdAt = new Date();
}
toJSON() {
return {
id: this.id,
name: this.name,
createdAt: this.createdAt.toISOString()
};
}
}
Serialization considerations:
- Dates become strings — need reviver to restore
- Functions, undefined, symbols are lost
- Maps and Sets become empty objects
- BigInt throws TypeError
- Circular references throw TypeError
Data Modeling
Relational Modeling
// One-to-many: User → Posts
const user = { id: 1, name: 'Alice' };
const posts = [
{ id: 1, userId: 1, title: 'Post 1' },
{ id: 2, userId: 1, title: 'Post 2' }
];
// Many-to-many: Posts ↔ Tags
const postTags = [
{ postId: 1, tagId: 1 },
{ postId: 1, tagId: 2 },
{ postId: 2, tagId: 1 }
];
Domain-Driven Modeling
// Model real-world concepts as entities and value objects
class Order {
constructor(id, customerId) {
this.id = id; // Entity (has identity)
this.customerId = customerId;
this.items = []; // Value objects
this.status = 'pending'; // State
}
addItem(product, quantity) {
this.items.push({ product, quantity });
}
submit() {
if (this.items.length === 0) throw new Error('Empty order');
this.status = 'submitted';
}
}
Event Sourcing
// Store events, not current state
const events = [
{ type: 'OrderCreated', orderId: 1, customerId: 1 },
{ type: 'ItemAdded', orderId: 1, product: 'Book', quantity: 2 },
{ type: 'ItemAdded', orderId: 1, product: 'Pen', quantity: 1 },
{ type: 'OrderSubmitted', orderId: 1 }
];
// Rebuild current state from events
function rebuildState(events) {
return events.reduce((state, event) => {
switch (event.type) {
case 'OrderCreated':
return { id: event.orderId, items: [], status: 'draft' };
case 'ItemAdded':
return { ...state, items: [...state.items, event] };
case 'OrderSubmitted':
return { ...state, status: 'submitted' };
default:
return state;
}
}, null);
}
Best Practices
Do's
// ✅ Normalize relational data
// ✅ Use unique IDs for entities
// ✅ Separate UI state from domain state
// ✅ Serialize dates explicitly
// ✅ Model data before coding
// ✅ Use TypeScript interfaces for data shapes
Don'ts
// ❌ Don't deeply nest related data
// ❌ Don't store derived values (calculate on read)
// ❌ Don't mix concerns (UI state in domain models)
// ❌ Don't serialize functions or class instances
// ❌ Don't use arrays for entity lookups (use objects/maps)
Summary Table
| Concept | Purpose | Pattern |
|---|---|---|
| State | Data that changes over time | Separate UI/domain/server state |
| Entities | Objects with identity | Unique ID, mutable properties |
| Normalization | Flat data structure | byId + allIds pattern |
| Serialization | Data transfer format | JSON with custom toJSON() |
| Modeling | Data structure design | Relational, DDD, event sourcing |
Conclusion
Good data modeling is the foundation of good software:
- Understand your state — What changes? Where does it live?
- Normalize early — Flat structures are easier to work with
- Model entities carefully — Identity matters more than values
- Serialize thoughtfully — JSON has limitations you must handle
Invest time in data modeling before writing code, and your application architecture will be stronger for it.
#data-modeling #state-management #javascript #normalization #architecture #web-development