0
# Associations
1
2
Model relationship definitions and operations including one-to-one, one-to-many, and many-to-many associations with eager loading.
3
4
## Capabilities
5
6
### BelongsTo Association
7
8
Defines a belongs-to relationship where the current model has a foreign key pointing to the target model.
9
10
```typescript { .api }
11
/**
12
* Define belongs-to association
13
* @param target - Target model class
14
* @param options - Association options
15
* @returns BelongsTo association instance
16
*/
17
static belongsTo(target: ModelCtor, options?: BelongsToOptions): BelongsTo;
18
19
interface BelongsToOptions {
20
/** Foreign key column name */
21
foreignKey?: string | ForeignKeyOptions;
22
/** Target key (usually primary key) */
23
targetKey?: string;
24
/** Association alias */
25
as?: string;
26
/** Association scope */
27
scope?: AssociationScope;
28
/** Include hooks */
29
hooks?: boolean;
30
/** On update action */
31
onUpdate?: string;
32
/** On delete action */
33
onDelete?: string;
34
/** Constraint options */
35
constraints?: boolean;
36
}
37
38
interface ForeignKeyOptions {
39
/** Column name */
40
name?: string;
41
/** Allow null values */
42
allowNull?: boolean;
43
}
44
```
45
46
**Usage Examples:**
47
48
```typescript
49
// Basic belongs-to
50
class Post extends Model {}
51
class User extends Model {}
52
53
Post.belongsTo(User); // Creates userId foreign key on Post
54
55
// With custom foreign key
56
Post.belongsTo(User, {
57
foreignKey: 'authorId',
58
as: 'author'
59
});
60
61
// With constraints
62
Post.belongsTo(User, {
63
foreignKey: {
64
name: 'userId',
65
allowNull: false
66
},
67
onDelete: 'CASCADE',
68
onUpdate: 'CASCADE'
69
});
70
71
// Usage in queries
72
const post = await Post.findOne({
73
include: [{ model: User, as: 'author' }]
74
});
75
76
// Instance methods (automatically created)
77
const post = await Post.findByPk(1);
78
const user = await post.getUser(); // or getAuthor() if aliased
79
await post.setUser(newUser);
80
const newUser = await post.createUser({ name: 'John' });
81
```
82
83
### HasOne Association
84
85
Defines a has-one relationship where the target model has a foreign key pointing to the current model.
86
87
```typescript { .api }
88
/**
89
* Define has-one association
90
* @param target - Target model class
91
* @param options - Association options
92
* @returns HasOne association instance
93
*/
94
static hasOne(target: ModelCtor, options?: HasOneOptions): HasOne;
95
96
interface HasOneOptions {
97
/** Foreign key column name on target */
98
foreignKey?: string | ForeignKeyOptions;
99
/** Source key (usually primary key) */
100
sourceKey?: string;
101
/** Association alias */
102
as?: string;
103
/** Association scope */
104
scope?: AssociationScope;
105
/** Include hooks */
106
hooks?: boolean;
107
/** On update action */
108
onUpdate?: string;
109
/** On delete action */
110
onDelete?: string;
111
/** Constraint options */
112
constraints?: boolean;
113
}
114
```
115
116
**Usage Examples:**
117
118
```typescript
119
// Basic has-one
120
class User extends Model {}
121
class Profile extends Model {}
122
123
User.hasOne(Profile); // Creates userId foreign key on Profile
124
125
// With options
126
User.hasOne(Profile, {
127
foreignKey: 'ownerId',
128
as: 'userProfile',
129
onDelete: 'CASCADE'
130
});
131
132
// Usage in queries
133
const user = await User.findOne({
134
include: [Profile] // or { model: Profile, as: 'userProfile' }
135
});
136
137
// Instance methods
138
const user = await User.findByPk(1);
139
const profile = await user.getProfile();
140
await user.setProfile(newProfile);
141
const profile = await user.createProfile({ bio: 'Hello' });
142
```
143
144
### HasMany Association
145
146
Defines a has-many relationship where the target model has a foreign key pointing to the current model.
147
148
```typescript { .api }
149
/**
150
* Define has-many association
151
* @param target - Target model class
152
* @param options - Association options
153
* @returns HasMany association instance
154
*/
155
static hasMany(target: ModelCtor, options?: HasManyOptions): HasMany;
156
157
interface HasManyOptions {
158
/** Foreign key column name on target */
159
foreignKey?: string | ForeignKeyOptions;
160
/** Source key (usually primary key) */
161
sourceKey?: string;
162
/** Association alias */
163
as?: string;
164
/** Association scope */
165
scope?: AssociationScope;
166
/** Include hooks */
167
hooks?: boolean;
168
/** On update action */
169
onUpdate?: string;
170
/** On delete action */
171
onDelete?: string;
172
/** Constraint options */
173
constraints?: boolean;
174
}
175
```
176
177
**Usage Examples:**
178
179
```typescript
180
// Basic has-many
181
class User extends Model {}
182
class Post extends Model {}
183
184
User.hasMany(Post); // Creates userId foreign key on Post
185
186
// With options
187
User.hasMany(Post, {
188
foreignKey: 'authorId',
189
as: 'articles',
190
scope: { status: 'published' }
191
});
192
193
// Usage in queries
194
const user = await User.findOne({
195
include: [{
196
model: Post,
197
as: 'articles',
198
where: { status: 'published' }
199
}]
200
});
201
202
// Instance methods
203
const user = await User.findByPk(1);
204
const posts = await user.getPosts(); // or getArticles()
205
await user.setPosts([post1, post2]);
206
await user.addPost(newPost);
207
await user.addPosts([post1, post2]);
208
await user.removePost(post);
209
await user.removePosts([post1, post2]);
210
const newPost = await user.createPost({ title: 'Hello' });
211
const hasPost = await user.hasPost(post);
212
const postCount = await user.countPosts();
213
```
214
215
### BelongsToMany Association
216
217
Defines a many-to-many relationship through a junction table.
218
219
```typescript { .api }
220
/**
221
* Define belongs-to-many association
222
* @param target - Target model class
223
* @param options - Association options
224
* @returns BelongsToMany association instance
225
*/
226
static belongsToMany(target: ModelCtor, options: BelongsToManyOptions): BelongsToMany;
227
228
interface BelongsToManyOptions {
229
/** Junction table name or model */
230
through: string | ModelCtor | ThroughOptions;
231
/** Foreign key for source model */
232
foreignKey?: string | ForeignKeyOptions;
233
/** Foreign key for target model */
234
otherKey?: string | ForeignKeyOptions;
235
/** Source key (usually primary key) */
236
sourceKey?: string;
237
/** Target key (usually primary key) */
238
targetKey?: string;
239
/** Association alias */
240
as?: string;
241
/** Association scope */
242
scope?: AssociationScope;
243
/** Include hooks */
244
hooks?: boolean;
245
/** Constraint options */
246
constraints?: boolean;
247
}
248
249
interface ThroughOptions {
250
/** Junction model */
251
model: ModelCtor;
252
/** Unique constraint on junction */
253
unique?: boolean;
254
/** Additional attributes on junction */
255
scope?: any;
256
}
257
```
258
259
**Usage Examples:**
260
261
```typescript
262
// Basic many-to-many
263
class User extends Model {}
264
class Role extends Model {}
265
266
User.belongsToMany(Role, { through: 'UserRoles' });
267
Role.belongsToMany(User, { through: 'UserRoles' });
268
269
// With custom keys
270
User.belongsToMany(Role, {
271
through: 'UserRoles',
272
foreignKey: 'userId',
273
otherKey: 'roleId',
274
as: 'permissions'
275
});
276
277
// With junction model
278
class UserRole extends Model {}
279
UserRole.init({
280
userId: DataTypes.INTEGER,
281
roleId: DataTypes.INTEGER,
282
assignedAt: DataTypes.DATE,
283
assignedBy: DataTypes.INTEGER
284
});
285
286
User.belongsToMany(Role, {
287
through: UserRole,
288
foreignKey: 'userId',
289
otherKey: 'roleId'
290
});
291
292
// Usage in queries
293
const user = await User.findOne({
294
include: [{
295
model: Role,
296
through: { attributes: ['assignedAt'] } // Include junction attributes
297
}]
298
});
299
300
// Instance methods
301
const user = await User.findByPk(1);
302
const roles = await user.getRoles();
303
await user.setRoles([role1, role2]);
304
await user.addRole(newRole);
305
await user.addRoles([role1, role2]);
306
await user.removeRole(role);
307
await user.removeRoles([role1, role2]);
308
309
// With junction attributes
310
await user.addRole(role, { through: { assignedBy: adminId } });
311
```
312
313
### Eager Loading
314
315
Loading associated models along with the main query.
316
317
```typescript { .api }
318
interface IncludeOptions {
319
/** Model to include */
320
model: ModelCtor;
321
/** Association alias */
322
as?: string;
323
/** Include attributes */
324
attributes?: FindAttributeOptions;
325
/** WHERE clause for included model */
326
where?: WhereOptions;
327
/** Required join (INNER vs LEFT JOIN) */
328
required?: boolean;
329
/** Include paranoid records */
330
paranoid?: boolean;
331
/** Nested includes */
332
include?: IncludeOptions | IncludeOptions[];
333
/** Separate queries for has-many */
334
separate?: boolean;
335
/** Limit included records */
336
limit?: number;
337
/** Through table options (for belongsToMany) */
338
through?: ThroughOptions;
339
/** Duplicate records handling */
340
duplicating?: boolean;
341
}
342
343
type Includeable = ModelCtor | IncludeOptions;
344
```
345
346
**Usage Examples:**
347
348
```typescript
349
// Basic include
350
const users = await User.findAll({
351
include: [Post]
352
});
353
354
// Include with alias
355
const users = await User.findAll({
356
include: [{ model: Post, as: 'articles' }]
357
});
358
359
// Include with conditions
360
const users = await User.findAll({
361
include: [{
362
model: Post,
363
where: { status: 'published' },
364
required: true // INNER JOIN instead of LEFT JOIN
365
}]
366
});
367
368
// Nested includes
369
const posts = await Post.findAll({
370
include: [{
371
model: User,
372
include: [Profile]
373
}]
374
});
375
376
// Many-to-many with junction attributes
377
const users = await User.findAll({
378
include: [{
379
model: Role,
380
through: {
381
attributes: ['assignedAt', 'assignedBy']
382
}
383
}]
384
});
385
386
// Separate queries for performance
387
const users = await User.findAll({
388
include: [{
389
model: Post,
390
separate: true, // Run separate query
391
limit: 5
392
}]
393
});
394
```
395
396
### Association Scopes
397
398
Define scopes that are automatically applied to associations.
399
400
```typescript { .api }
401
interface AssociationScope {
402
[key: string]: any;
403
}
404
```
405
406
**Usage Example:**
407
408
```typescript
409
// Define association with scope
410
User.hasMany(Post, {
411
as: 'publishedPosts',
412
scope: {
413
status: 'published'
414
}
415
});
416
417
// Scope is automatically applied
418
const user = await User.findByPk(1);
419
const publishedPosts = await user.getPublishedPosts();
420
// Only returns posts where status = 'published'
421
```
422
423
### Advanced Association Patterns
424
425
Complex association setups and patterns.
426
427
```typescript { .api }
428
// Self-referencing associations
429
User.hasMany(User, { as: 'Children', foreignKey: 'parentId' });
430
User.belongsTo(User, { as: 'Parent', foreignKey: 'parentId' });
431
432
// Multiple associations between same models
433
User.hasMany(Post, { as: 'AuthoredPosts', foreignKey: 'authorId' });
434
User.hasMany(Post, { as: 'EditedPosts', foreignKey: 'editorId' });
435
436
// Polymorphic associations (manual setup)
437
class Comment extends Model {}
438
Comment.belongsTo(Post, { foreignKey: 'commentableId', constraints: false, scope: { commentableType: 'post' } });
439
Comment.belongsTo(User, { foreignKey: 'commentableId', constraints: false, scope: { commentableType: 'user' } });
440
```
441
442
**Usage Examples:**
443
444
```typescript
445
// Self-referencing
446
const manager = await User.findOne({
447
include: [{ model: User, as: 'Children' }]
448
});
449
450
// Multiple associations
451
const user = await User.findOne({
452
include: [
453
{ model: Post, as: 'AuthoredPosts' },
454
{ model: Post, as: 'EditedPosts' }
455
]
456
});
457
458
// Polymorphic
459
const comments = await Comment.findAll({
460
include: [
461
{ model: Post, where: { commentableType: 'post' } },
462
{ model: User, where: { commentableType: 'user' } }
463
]
464
});
465
```