aboutsummaryrefslogtreecommitdiff
path: root/files/vi/learn/server-side/express_nodejs/mongoose/index.html
blob: fdf6531d75a29bd582be83c43c40b9dc38e4a20a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
---
title: 'Hướng dẫn Express Phần 3: Sử dụng Database (với Mongoose)'
slug: Learn/Server-side/Express_Nodejs/mongoose
translation_of: Learn/Server-side/Express_Nodejs/mongoose
---
<div>{{LearnSidebar}}</div>

<div>{{PreviousMenuNext("Learn/Server-side/Express_Nodejs/skeleton_website", "Learn/Server-side/Express_Nodejs/routes", "Learn/Server-side/Express_Nodejs")}}</div>

<p class="summary">Bài viết này giới thiệu tổng quan về cơ sở dữ liệu và cách dùng chúng với các ứng dụng Node/Express. Sau đó nó sẽ chỉ cho ta thấy cách sử dụng <a href="http://mongoosejs.com/">Mongoose</a> để tạo ra kết nối đến cơ sở dữ liệu cho trang web <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Tutorial_local_library_website">LocalLibrary</a>. Nó giải thích cách mà schema và model của đối tượng được định nghĩa, các kiểu trường chính, và cách thức xác thực cơ bản. Nó còn trình bày một số cách chính để bạn có thể truy cập tới dữ liệu của model.</p>

<table class="learn-box standard-table">
 <tbody>
  <tr>
   <th scope="row">Bài viết trước:</th>
   <td><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/skeleton_website">Express Tutorial Part 2: Creating a skeleton website</a></td>
  </tr>
  <tr>
   <th scope="row">Mục tiêu:</th>
   <td>Có thể thiết kế và tự tạo model của riêng mình thông qua Mongoose.</td>
  </tr>
 </tbody>
</table>

<h2 id="Khái_quát">Khái quát</h2>

<p>Thủ thư sẽ dùng trang web Local Library để lưu trữ thông tin về sách và người mượn, trong khi các bạn đọc sẽ dùng nó để kiếm sách, tìm xem có bao nhiêu cuốn có sẵn, và tiếp tục như thế hoặc làm thủ tục mượn sách. Để có thể lưu trữ và truy xuất thông tin một cách hiệu quả, ta sẽ lưu trữ tất cả trong một <em>cơ sở dữ liệu</em>.</p>

<p>Các ứng dụng Express có thể dùng nhiều loại cơ sở dữ liệu khác nhau, và có khá là nhiều hướng tiếp cận để bạn có thể thi hành <strong>C</strong>reate, <strong>R</strong>ead, <strong>U</strong>pdate and <strong>D</strong>elete (CRUD). Bài viết này sẽ cung cấp khái quát một số lựa chọn có thể áp dụng, và sẽ đi vào phân tích một phương pháp nhất định.</p>

<h3 id="Tôi_có_thể_dùng_cơ_sở_dữ_liệu_nào">Tôi có thể dùng cơ sở dữ liệu nào?</h3>

<p>Ứng dụng<em> Express</em> có thể dùng bất cứ cơ sở dữ liệu nào được hỗ trợ bởi <em>Node</em> (Chính <em>Express</em> không đưa ra bất cứ đặc tả chỉ tiết hành vi hay ràng buộc nào về hệ quản trị cơ sở dữ liệu). Thành ra có <a href="https://expressjs.com/en/guide/database-integration.html">rất nhiều thứ</a> để bạn thoả sức chọn lựa, bao gồm PostgreSQL, MySQL, Redis, SQLite, và MongoDB.</p>

<p>Khi chọn một cơ sở dữ liệu, bạn nên cân nhắc những thứ như là độ khó, hiệu năng, dễ dàng bảo trì, chi phí, sự hỗ trợ của cộng đồng, vân vân và mây mây. Do chưa có cơ sở dữ liệu nào đạt được danh hiệu 'tốt nhất', nên ta có thể lựa chọn hầu như mọi giải pháp vừa kể trên cho một trang cỡ nhỏ tới vừa như trang Local Library của chúng ta.</p>

<p>Để biết thêm chi tiết để tiện đường lựa chọn: <a href="https://expressjs.com/en/guide/database-integration.html">Database integration</a> (Tài liệu của Express).</p>

<h3 id="Cách_nào_tốt_nhất_để_thao_tác_với_cơ_sở_dữ_liệu">Cách nào tốt nhất để thao tác với cơ sở dữ liệu?</h3>

<p>Có hai hướng tiếp cận để tương tác với một cơ sở dữ liệu: </p>

<ul>
 <li>Sử dụng ngôn ngữ truy vấn của riêng cơ sở dữ liệu đó (ví dụ như SQL)</li>
 <li>Sử dụng Object Data Model ("ODM") / Object Relational Model ("ORM"). ODM/ORM đại diện cho dữ liệu của trang web dưới dạng đối tượng trong JavaScript, sau đó ánh xạ tới cơ sở dữ liệu bên dưới. Một vài ORMs được gắn với một cơ sở dữ liệu nào đó, trong khi số khác chỉ là một cầu nối giữa cơ sở dữ liệu và phần code backend.</li>
</ul>

<p>Sử dụng ngôn ngữ truy vấn được hỗ trợ bởi cơ sở dữ liệu (như là SQL) sẽ đạt được <em>hiệu suất</em> cao nhất. ODM thường chậm hơn bởi nó phải thông dịch mã để có thể truy vấn giữa đối tượng và định dạng của cơ sở dữ liệu, mà không dùng được các truy vấn hiệu quả nhất của cơ sở dữ liệu (điều này càng nghiêm trọng hơn khi ORM được sử dụng cho nhiều dạng cơ sở dữ liệu khác nhau, và phải tạo ra nhiều điều khoản lằng nhằng hơn đối với lượng tính năng được cơ sở dữ liệu hỗ trợ).</p>

<p>Điểm mạnh của ORM là lập trình viên có thể giữ tư duy như với đối tượng của JavaScript thay vì phải ngôn ngữ thuần tuý viết riêng cho cơ sở dữ liệu — điều này càng đúng khi bạn phải làm việc với nhiều loại cơ sở dữ liệu (trên cùng hoặc khác trang web). ORM còn cung cấp các tính năng để thực hiện xác thực và kiểm tra dữ liệu.</p>

<div class="note">
<p><strong>Mẹo:</strong>  Sử dụng ODM/ORMs thường giúp giảm thiểu chi phí phát triển và bảo trì! Trừ khi bạn đã quá thân thuộc với ngôn ngữ truy vấn thuần tuý hoặc hiệu suất là trên hết, bạn nên cân nhắc đến việc sử dụng ODM.</p>
</div>

<h3 id="Tôi_nên_dùng_ORMODM_nào">Tôi nên dùng ORM/ODM nào?</h3>

<p>Có nhiều giải pháp cho ODM/ORM có sẵn trên trang quản lý gói NPM (tìm theo nhãn <a href="https://www.npmjs.com/browse/keyword/odm">odm</a> và <a href="https://www.npmjs.com/browse/keyword/orm">orm</a>!).</p>

<p>Vào thời điểm viết bài này có một số giải pháp phổ biến như sau:</p>

<ul>
 <li><a href="https://www.npmjs.com/package/mongoose">Mongoose</a>: Mongoose là một công cụ mô hình hoá đối tượng <a href="https://www.mongodb.org/">MongoDB</a>, được thiết kế để làm việc trên môi trường bất đồng bộ.</li>
 <li><a href="https://www.npmjs.com/package/waterline">Waterline</a>: Một ORM trích xuất từ <a href="http://sailsjs.com/">Sails</a> framework, có nền tảng là Express. Nó cung cấp một bộ API tiêu chuẩn để truy cập vào vô số kiểu cơ sở dữ liệu khác nhau, bao gồm Redis, mySQL, LDAP, MongoDB, và Postgres.</li>
 <li><a href="https://www.npmjs.com/package/bookshelf">Bookshelf</a>: Trên nền promise và giao diện callback truyền thống, cung cấp hỗ trợ transaction, eager/nested-eager relation loading, sự kết hợp đa hình, and hỗ trợ quan hệ một-một, một-nhiều, nhiều-nhiều. Làm việc với PostgreSQL, MySQL, và SQLite3.</li>
 <li><a href="https://www.npmjs.com/package/objection">Objection</a>: Vận dụng hết sức mạnh của SQL và hạ tầng cơ sở dữ liệu bên dưới (hỗ trợ SQLite3, Postgres, và MySQL) theo cách dễ dàng nhất có thể.</li>
 <li>
  <p><a href="https://www.npmjs.com/package/sequelize">Sequelize</a> là một ORM nền Promise dành cho Node.js và io.js. Nó hỗ trợ biên dịch PostgreSQL, MySQL, MariaDB, SQLite và MSSQL và hỗ trợ giao tác cứng, các quan hệ, read replication và nhiều hơn thế.</p>
 </li>
 <li>
  <p><a href="https://node-orm.readthedocs.io/en/latest/">Node ORM2 </a>là Trình quản lý mối quan hệ đối tượng cho NodeJS. Nó hỗ trợ MySQL, SQLite và Progress, giúp làm việc với cơ sở dữ liệu bằng cách sử dụng phương pháp hướng đối tượng.</p>
 </li>
 <li>
  <p><a href="http://1602.github.io/jugglingdb/">JugglingDB </a>là ORM DB chéo cho NodeJS, cung cấp giao diện chung để truy cập hầu hết các định dạng cơ sở dữ liệu phổ biến. Hiện đang hỗ trợ MySQL, SQLite3, Postgres, MongoDB, Redis và js-memory-Storage (công cụ tự viết để chỉ sử dụng thử nghiệm).</p>
 </li>
</ul>

<p>Như một luật ngầm định, bạn nên cân nhắc cả tính năng được công bố cũng như "hoạt động cộng đồng" (tải xuống, sự đóng góp, báo lỗi, chất lượng của tài liệu, vân vân và mây mây) khi lựa chọn một giải pháp. Vào thời điểm viết bài thì Mongoose là một ORM phổ biến nhất, và là lựa chọn hợp lý nếu bạn dùng MongoDB làm cơ sở dữ liệu của mình.</p>

<h3 id="Sử_dụng_Mongoose_và_MongoDb_cho_LocalLibrary">Sử dụng Mongoose và MongoDb cho LocalLibrary</h3>

<p>Đối với ví dụ <em>Local Library</em> (và cho cả phần còn lại của bài viết này) ta sẽ sử dụng <a href="https://www.npmjs.com/package/mongoose">Mongoose ODM</a> để truy cập dữ liệu thư viện của chúng ta. Mongoose hoạt động như một frontend của <a href="https://www.mongodb.com/what-is-mongodb">MongoDB</a>, cơ sở dữ liệu mở dạng <a href="https://en.wikipedia.org/wiki/NoSQL">NoSQL</a> sử dụng mô hình dữ liệu hướng document. Một “collection” và “documents”, trong cơ sở dữ liệu MongoDB, <a href="https://docs.mongodb.com/manual/core/databases-and-collections/#collections">tương tự với</a> một “table” và “row” trong cơ sở dữ liệu quan hệ.</p>

<p>ODM và kết hợp cơ sở dữ liệu này cực kì phổ biến trong cộng đồng Node, phần lớn là bởi kho chứa document và hệ thống truy vấn khá là giống với JSON, vã dĩ nhiên rất đỗi thân quen với các lập trình viên JavaScript.</p>

<div class="note">
<p><strong>Mẹo:</strong> Bạn không cần phải biết gì về MongoDB để có thể sử dụng được Mongoose, mặc dù vài phần trong <a href="http://mongoosejs.com/docs/guide.html">tài liệu của Mongoose</a> <em>sẽ</em> dễ đọc hiểu hơn nếu bạn đã quen với MongoDB rồi.</p>
</div>

<p>Phần còn lại của bài viết này hướng dẫn cách để định nghĩa và truy cập schema và model của Mongoose thông qua ví dụ làm trang web cho <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Tutorial_local_library_website">LocalLibrary</a>.</p>

<h2 id="Thiết_kế_model_LocalLibrary">Thiết kế model LocalLibrary</h2>

<p>Trước khi nhảy bổ vào viết code cho model, sẽ tốt hơn nếu ta dành vài phút để nghĩ về dữ liệu ta cần phải lưu trữ và mối quan hệ giữa các đối tượng khác nhau.</p>

<p>Chúng ta biết rằng chúng ta cần phải lữu trữ dữ liệu về sách (tựa đề, tóm tắt, tác giả, thể loại, mã số tiêu chuẩn quốc tế cho sách) và sẽ có khá nhiều cuốn giống nhau (với mã số quốc tế riêng biệt, tình trạng, vân vân.). Có lẽ ta sẽ cần lưu trữ nhiều thông tin về tác giả hơn chỉ là tên của người đó, và có vô số tác giả trùng hoặc có tên na ná nhau. Chúng ta muốn phân loại thông tin dựa theo tựa đề, tác giả, thể loại, và kiểu sách.</p>

<p>Công cuộc thiết kế model yêu cầu thiết kế các mô hình riêng rẽ cho từng "object" (nhóm các thông tin có liên quan với nhau). Trong trường hợp này thì các object ấy hẳn là sách, các thuộc tính của sách, và tác giả.</p>

<p>Bạn chắc hẳn sẽ muốn biểu diễn mô hình dưới dạng danh sách liệt kê (ví dụ như một danh sách các lựa chọn), thay vì code cứng tất tần tật — việc này được đề nghị khi các lựa chọn vẫn chưa được liệt kê hết hoặc có thể bị thay đổi. Ứng viên sáng giá cho việc mô hình hoá này chính là thể loại sách (ví dụ như Khoa học Viễn tưởng, Ngôn tình, hoặc gì đó tương tự.)</p>

<p>Khi đã quyết định được các mô hình và trường dữ liệu, ta cần phải suy ngẫm về mối quan hệ giữa chúng.</p>

<p>Để làm tốt việc này, ta sẽ dùng biểu đồ liên hệ UML như bên dưới để biểu diễn các model ta sắp định nghĩa ra (trong các hộp). Như đã nói ở trên, ta vừa tạo ra model cho sách (chi tiết cơ bản nhất cho sách), phần tử của sách (lượng bản sách còn trong hệ thống), và tác giả. Chúng ta cũng vừa quyết định sẽ tạo thêm model cho thể loại, để giá trị của nó thay đổi động. Ta cũng vừa quyết định sẽ không tạo mô hình cho <code>BookInstance:status</code> — chúng ta sẽ code cứng phần này đến một giá trị có thể chấp nhận được bởi ta không mong muốn giá trị của nó bị thay đổi. Bạn có thể thấy trong mỗi hộp là tên của mô hình, tên của các trường và kiểu dữ liệu tương ứng, đồng thời cả các thuộc tính và kiểu trả về nữa.</p>

<p>Biểu đồ còn chỉ ra mối quan hệ giữa các mô hình, bao gồm cả <em>bội số</em>. Bội số là các con số nhỏ nhỏ nằm trên các đường thẳng nối các hộp lại với nhau (lớn nhất và nhỏ nhất) để chỉ ra độ liên hệ trong các mối quan hệ giữa các mô hình với nhau. Láy ví dụ như trong hình dưới, những đoạn kẻ nối giữa các hộp biểu diễn rằng <code>Book</code> và <code>Genre</code> liên quan đến nhau. Con số nằm gần với mô hình <code>Book</code> chỉ ra rằng Book phải có từ 0 đến nhiều <code>Genre</code> (bao nhiêu tuỳ thích), trong khi con số nằm ở đầu đoạn bên kia của <code>Genre</code> lại chỉ ra rằng nó có 0 hoặc nhiều liên hệ với Book.</p>

<div class="note">
<p><strong>Lưu ý</strong>: Như đã nói trong <a href="#related_documents">Mongoose primer</a> phía dưới, thường sẽ tốt hơn nếu có một trường riêng để định nghĩa mối quan hệ giữa documents/models chỉ trong <em>một</em> mô hình (bạn vẫn có thể tìm thấy mối quan hệ ngược lại bằng cách tìm kiếm <code>_id</code> liên quan trong các mô hình khác). Bên dưới biểu diễn mối quan hệ giữa Book/Genre and Book/Author trong Book schema, và mối quan hệ giữa Book/BookInstance trong BookInstance Schema. Việc lựa chọn này hơi cảm tính — ta hoàn toàn có thể định nghĩa các trường trong một schema khác.</p>
</div>

<p><img alt="Mongoose Library Model  with correct cardinality" src="https://mdn.mozillademos.org/files/15645/Library%20Website%20-%20Mongoose_Express.png" style="height: 620px; width: 737px;"></p>

<div class="note">
<p><strong>Lưu ý</strong>: Phần tiếp theo cung cấp kiến thức cơ bản giải thích cách mô hình được định nghĩa và sử dụng. Ta sẽ tìm cách để xây dựng đống mô hình vừa vẽ ra trong biểu đồ trên.</p>
</div>

<h2 id="Mongoose_primer">Mongoose primer</h2>

<p>Phần này giới thiệu khái quát cách để kết nối Mongoose với một cơ sở dữ liệu MongoDB, cách định nghĩa một schema và một model, và cách viết vài câu truy vấn đơn giản. </p>

<div class="note">
<p><strong>Lưu ý:</strong> Cái primer này "bị ảnh hưởng mạnh" bởi <a href="https://www.npmjs.com/package/mongoose">Mongoose quick start</a> trên <em>npm</em> và <a href="http://mongoosejs.com/docs/guide.html">official documentation</a>.</p>
</div>

<h3 id="Cài_đặt_Mongoose_và_MongoDB">Cài đặt Mongoose và MongoDB</h3>

<p>Mongoose được cài đặt vào trong project của bạn (<strong>package.json</strong>) giống hệt như các dependency khác — dùng NPM. Để cài đặt nó, chạy câu lệnh sau trong thư mục project của bạn:</p>

<pre class="brush: bash"><code>npm install mongoose</code>
</pre>

<p>Sau khi cài xong, <em>Mongoose</em> sẽ tự động thêm mọi dependencies của nó, bao gồm cả driver cơ sở dữ liệu cho MongoDB, nhưng nó sẽ không cài đặt MongoDB đâu nhé. Nếu bạn muốn cài đặt một máy chủ MongoDB thì bạn có thể <a href="https://www.mongodb.com/download-center">tải xuống bộ cài tại đây</a>, dành cho nhiều vô số hệ điều hành khác nhau và cài đặt nó trên hệ thống của mình. Bạn cũng có thể sử dụng MongoDB trên đám mây.</p>

<div class="note">
<p><strong>Lưu ý:</strong> Trong bài viết này, ta sẽ sử dụng mLab, một <em>cơ sở dữ liệu được cung cấp dưới dạng dịch vụ</em> trên nền tảng điện toán đám mây và chọn <a href="https://mlab.com/plans/pricing/">sandbox tier</a> nhé. Cái này khá hợp với khâu phát triển, và không phụ thuộc vào việc "cài đặt" hệ điều hành (cơ-sở-dữ-liệu-cung-cấp-dưới-dạng-dịch-vụ là một hướng tiếp cận nếu sử dụng trong dự án thật).</p>
</div>

<h3 id="Kết_nối_với_MongoDB">Kết nối với MongoDB</h3>

<p><em>Mongoose</em> yêu cầu một kết nối tới cơ sở dữ liệu MongoDB. Bạn có thể <code>require()</code> và kết nối cục bộ tới cơ sở dữ liệu thông qua <code>mongoose.connect()</code>, như bên dưới.</p>

<pre class="brush: js">//Nhập mô-đun mongoose
var mongoose = require('mongoose');

//Thiết lập một kết nối mongoose mặc định
var mongoDB = 'mongodb://127.0.0.1/my_database';
mongoose.connect(mongoDB);
//Ép Mongoose sử dụng thư viện promise toàn cục
mongoose.Promise = global.Promise;
//Lấy kết nối mặc định
var db = mongoose.connection;

//Ràng buộc kết nối với sự kiện lỗi (để lấy ra thông báo khi có lỗi)
db.on('error', console.error.bind(console, 'MongoDB connection error:'));</pre>

<p>Bạn có thể lấy ra đối tượng mặc định <code>Connection</code> với <code>mongoose.connection</code>. Ngay khi đã kết nối, sự kiện sẽ nổ ra trên thuộc tính <code>Connection</code>.</p>

<div class="note">
<p><strong>Mẹo:</strong> Nếu bạn muốn thêm mới các kết nối khác thì có thể dùng <code>mongoose.createConnection()</code>. Vẫn dùng chung định dạng URI (bao gồm máy chủ, cơ sở dữ liệu, cổng, lựa chọn khác.) như <code>connect()</code> và trả về một đối tượng <code>Connection</code>).</p>
</div>

<h3 id="Định_nghĩa_và_tạo_ra_các_model">Định nghĩa và tạo ra các model</h3>

<p>Model được <em>định nghĩa </em>thông qua giao diện <code>Schema</code>. Schema định nghĩa các trường được lưu trong mỗi document đi kèm với điều kiện xác thực và giá trị mặc định cho chúng. Hơn nữa, bạn có thể khởi tạo các thuộc tính tĩnh và phương thức hỗ trợ để làm việc với kiểu dữ liệu của bạn dễ dàng hơn, và cả các đặc tính ảo để có thể dùng như bất cứ trường nào, mà không bị lưu vào trong cơ sở dữ liệu (ta sẽ bàn về vấn đề này sau).</p>

<p>Schema sau đó được "biên dịch" thành mô hình qua phương thức <code>mongoose.model()</code>. Một khi đã có mô hình thì bạn có thể dùng nó để tìm, thêm, sửa, và xoá các đối tượng cùng kiểu.</p>

<div class="note">
<p><strong>Lưu ý:</strong> Mỗi mô hình có liên kết tới một <em>bộ sưu tập</em> các<em> tài liệu</em> trong cơ sở dữ liệu MongoDB. Documents sẽ chứa các trường/kiểu schema được định nghĩa trong mô hình <code>Schema</code>.</p>
</div>

<h4 id="Định_nghĩa_schema">Định nghĩa schema</h4>

<p>Đoạn code bên dưới trình bày cách cách thức tạo ra một Schema đơn giản. Đầu tiên bạn <code>require()</code> mongoose, rồi dùng phương thức khởi tạo của Schema để tạo ra một biến schema, định nghĩa một vài trường trong tham số truyền vào của phương thức khởi tạo.</p>

<pre class="brush: js">//Require Mongoose
var mongoose = require('mongoose');

//Định nghĩa một schema
var Schema = mongoose.Schema;

var SomeModelSchema = new Schema({
    a_string: String,
    a_date: Date
});
</pre>

<p>Trong trường hợp trên ta chỉ có 2 trường, một string và một date. Trong phần tiếp theo, ta sẽ thêm một vài trường khác, xác thực, và một số phương thức khác.</p>

<h4 id="Thêm_mới_một_mô_hình">Thêm mới một mô hình</h4>

<p>Mô hình được tạo ra từ schema qua phương thức <code>mongoose.model()</code>:</p>

<pre class="brush: js">// Định nghĩa schema
var Schema = mongoose.Schema;

var SomeModelSchema = new Schema({
    a_string: String,
    a_date: Date
});

<strong>// Biên dịch mô hình từ schema
var SomeModel = mongoose.model('SomeModel', SomeModelSchema );</strong></pre>

<p>Tham số thứ nhất là tên riêng cho collection sắp được tạo ra cho mô hình của bạn (Mongoose sẽ khởi tạo một collection cho model <em>SomeModel</em> ở phía trên), và tham số thứ hai là schema mà bạn muốn dùng để tạo ra mô hình.</p>

<div class="note">
<p><strong>Lưu ý:</strong> Khi đã có các model class, bạn có thể sử dụng chúng để thêm, sửa, hoặc xoá các bản ghi, và để chạy các câu truy vấn lấy tất cả các bản ghi hoặc tạo các tập hợp con cho một số lượng bản ghi nhất định. Ta sẽ tìm hiểu việc này trong phần <a href="#">Sử dụng mô hình</a>, và khi ta tạo khung nhìn.</p>
</div>

<h4 id="Kiểu_Schema_(các_trường)">Kiểu Schema (các trường)</h4>

<p>Một schema có thể có số trường thông tin tuỳ ý — mỗi trường đại diện cho một document lưu trong <em>MongoDB</em>. Schema trong ví dụ bên dưới trình bày các kiểu đơn giản của các trường cũng như cách định nghĩa chúng.</p>

<pre class="brush: js">var schema = new Schema(
{
  name: <strong>String</strong>,
  binary: <strong>Buffer</strong>,
  living: <strong>Boolean</strong>,
  updated: { type: <strong>Date</strong>, default: Date.now },
  age: { type: <strong>Number</strong>, min: 18, max: 65, required: true },
  mixed: <strong>Schema.Types.Mixed</strong>,
  _someId: <strong>Schema.Types.ObjectId</strong>,
  array: <strong>[]</strong>,
  ofString: [<strong>String</strong>], // Bạn có thể tạo mảng cho các trường khác
  nested: { stuff: { type: <strong>String</strong>, lowercase: true, trim: true } }
})</pre>

<p>Hầu hết các <a href="http://mongoosejs.com/docs/schematypes.html">SchemaTypes</a> (đống miêu tả sau từ “type:” hoặc sau tên trường) đều tự định nghĩa chính nó. Ngoại trừ:</p>

<ul>
 <li><code>ObjectId</code>: Đại diện cho các thuộc tính đặc trưng của mô hình trong cơ sở dữ liệu. Chẳng hạn, một cuốn sách có thể dùng thứ này để đại diện cho tác giả của nó. Nó còn sẽ chứa cả ID đặc trưng (<code>_id</code>) cho đối tượng đặc trưng. Ta có thể dùng phương thức <code>populate()</code> để lấy các thông tin liên quan nếu cần thiết.</li>
 <li><a href="http://mongoosejs.com/docs/schematypes.html#mixed">Mixed</a>: Một kiểu schema chồng chập.</li>
 <li><font face="Consolas, Liberation Mono, Courier, monospace">[]</font>: Mảng. Bạn có thể sử dụng các phương thức cho mảng riêng của JavaScript trên các mô hình này (push, pop, unshift, shift, reduce, vân vân và mây mây.). Ví dụ phía trên có một mảng đối tượng không định kiểu và một mảng đối tượng kiểu <code>String</code>, bạn vẫn có thể định nghĩa một mảng tuỳ ý kiểu đối tượng.</li>
</ul>

<p>Đoạn code cũng chỉ ra hai cách để khai báo một trường:</p>

<ul>
 <li><em>Tên</em> và <em>kiểu</em> của trường là một cặp khoá-giá trị (ví dụ như với các trường <code>name</code>, <code>binary</code> và <code>living</code>).</li>
 <li><em>Tên</em> trường chứa một đối tượng gồm có <code>type</code>, và nhiều <em>lựa chọn</em> khác. Lựa chọn bao gồm những thứ như là:
  <ul>
   <li>giá trị mặc định.</li>
   <li>công cụ xác thực định sẵn (như giá trị max/min) và các hàm tuỳ chỉnh.</li>
   <li>Trường ấy có bắt buộc (required) hay không</li>
   <li>Trường kiểu <code>String</code> nên tự động ở kiểu chữ nhỏ, chữ to, hoặc tỉa gọn (ví dụ <code>{ type: <strong>String</strong>, lowercase: true, trim: true }</code>)</li>
  </ul>
 </li>
</ul>

<p>Để biết thêm chi tiết, mời bạn xem thêm <a href="http://mongoosejs.com/docs/schematypes.html">SchemaTypes</a> (Tài liệu của Mongoose).</p>

<h4 id="Xác_thực">Xác thực</h4>

<p>Mongoose cung cấp một số hàm xác thực định sẵn và tuỳ chỉnh, và các hàm xác thực đồng bộ cũng như bất đồng bộ. Nó cho phép bạn đặc tả cả phạm vi chấp nhận hoặc giá trị và thông báo lỗi khi hàm xác thực gặp phải sự cố trong mọi trường hợp.</p>

<p>Các hàm xác thực định sẵn bao gồm:</p>

<ul>
 <li>Tất cả các <a href="http://mongoosejs.com/docs/schematypes.html">SchemaTypes</a> đều có hàm xác thực là <a href="http://mongoosejs.com/docs/api.html#schematype_SchemaType-required">required</a>. Hàm này xác minh rằng liệu trường dữ liệu đó có bắt buộc phải được cung cấp nếu muốn lưu lại vào trong document hay không.</li>
 <li><a href="http://mongoosejs.com/docs/api.html#schema-number-js">Numbers</a> có hai hàm là <a href="http://mongoosejs.com/docs/api.html#schema_number_SchemaNumber-min">min</a> và <a href="http://mongoosejs.com/docs/api.html#schema_number_SchemaNumber-max">max</a>.</li>
 <li><a href="http://mongoosejs.com/docs/api.html#schema-string-js">Strings</a> có:
  <ul>
   <li><a href="http://mongoosejs.com/docs/api.html#schema_string_SchemaString-enum">enum</a>: đặc tả tập các giá trị được cho phép truyền vào trường tương ứng.</li>
   <li><a href="http://mongoosejs.com/docs/api.html#schema_string_SchemaString-match">match</a>: đặc tả một regex để tạo luật cho xâu truyền vào.</li>
   <li><a href="http://mongoosejs.com/docs/api.html#schema_string_SchemaString-maxlength">maxlength</a> và <a href="http://mongoosejs.com/docs/api.html#schema_string_SchemaString-minlength">minlength</a> cho xâu truyền vào.</li>
  </ul>
 </li>
</ul>

<p>Ví dụ bên dưới (hơi khác một chút so với tài liệu của Mongoose) chỉ ra cách để thêm các hàm xác minh và thông báo lỗi:</p>

<pre class="brush: js"><code>
    var breakfastSchema = new Schema({
      eggs: {
        type: Number,
        min: [6, 'Too few eggs'],
        max: 12
        required: [true, 'Why no eggs?']
      },
      drink: {
        type: String,
        enum: ['Coffee', 'Tea', 'Water',]
      }
    });
</code></pre>

<p>Để biết thêm thông tin chi tiết về các hàm xác minh, hãy vào <a href="http://mongoosejs.com/docs/validation.html">Validation</a> (tài liệu của Mongoose) để tìm đọc.</p>

<h4 id="Thuộc_tính_ảo">Thuộc tính ảo</h4>

<p>Thuộc tính ảo là các thuộc tính của document mà bạn có thể lấy ra và đặt lại mà không làm ảnh hưởng tới MongoDB. Hàm lấy ra hiệu quả cho việc định dạng lại hoặc kết hợp các trường, trong khi hàm đặt lại lại hữu dụng cho việc phân tách một giá trị riêng lẻ thành nhiều giá trị trong cơ sở dữ liệu. Ví dụ trong tài liệu khởi tạo (và huỷ tạo) một thuộc tính ảo tên đầy đủ từ trường họ và tên, điều đó sẽ dễ dàng và sạch sẽ hơn thay vì tạo ra một trường họ tên mỗi khi có ai đó sử dụng mẫu.</p>

<div class="note">
<p><strong>Lưu ý:</strong> Ta sẽ dùng thuộc tính ảo trong thư viện để định nghĩa một URL đặc trưng cho từng bản ghi của mô hình thông qua một đường dẫn và giá trị của mỗi bản ghi <code>_id</code> của mỗi bản ghi.</p>
</div>

<p>Để biết thêm chi tiết hãy vào <a href="http://mongoosejs.com/docs/guide.html#virtuals">Virtuals</a> (tài liệu của Mongoose).</p>

<h4 id="Phương_thức_và_câu_truy_vấn_trợ_giúp">Phương thức và câu truy vấn trợ giúp</h4>

<p>Một schema có thể còn có <a href="http://mongoosejs.com/docs/guide.html#methods">phương thức biến</a>, <a href="http://mongoosejs.com/docs/guide.html#statics">phương thức tĩnh</a>, và <a href="http://mongoosejs.com/docs/guide.html#query-helpers">hỗ trợ truy vấn</a>. Phương thức biến và phương thức tĩnh gần như tương tự nhau, điểm khác biệt duy nhất là phương thức tĩnh liên kết với một bản ghi xác định và có quyền truy cập tới đối tượng hiện tại. Hỗ trợ truy vấn cho phép bạn mở rộng <a href="http://mongoosejs.com/docs/queries.html">chainable query builder API</a> của mongoose (ví dụ như, cho phép bạn thêm câu truy vấn "byName" sau các phương thức <code>find()</code>, <code>findOne()</code> và <code>findById()</code>).</p>

<h3 id="Sử_dụng_mô_hình">Sử dụng mô hình</h3>

<p>Ngay khi đã có một schema, bạn có thể dùng nó để tạo ra các mô hình. Mô hình đại diện cho một bộ sưu tập các tài liệu trong cơ sở dữ liệu mà bạn có thể tìm kiếm, trong khi các phần tử của mô hình đại diện cho từng tài liệu mà bạn có thể lưu trữ và truy xuất.</p>

<p>Chúng ta chỉ có thể tìm hiểu sơ qua như trên thôi. Nếu muốn chi tiết hơn thì hãy vào xem: <a href="http://mongoosejs.com/docs/models.html">Models</a> (tài liệu của Mongoose).</p>

<h4 id="Thêm_mới_và_chỉnh_sửa_tài_liệu">Thêm mới và chỉnh sửa tài liệu</h4>

<p>Để thêm mới một bản ghi, bạn có thể định nghĩa một phần tử của mô hình và sau đó dùng lời gọi <code>save()</code>. Ví dụ bên dưới chỉ ra rằng SomeModel là một đối tượng (chỉ có một trường là "name") mà ta vừa tạo ra từ schema của mình.</p>

<pre class="brush: js"><code>// Thêm mới một phần tử của mô hình SomeModel
var awesome_instance = new </code>SomeModel<code>({ name: 'awesome' });

// Lưu phần tử vừa thêm mới lại, thông qua việc truyền vào một hàm callback
awesome_instance.save(function (err) {
  if (err) return handleError(err);
  // saved!
});
</code></pre>

<p>Việc thêm bản ghi (đi kèm với sửa, xoá, và tìm kiếm) là các công việc bất đồng bộ — bạn phải truyền vào một hàm callback sau khi công việc hoàn tất. API sử dụng quy ước lỗi-trước, thế nên tham số thứ nhất trong hàm callback luôn là một giá trị lỗi (hoặc null). Nếu API trả về kết quả nào đó, nó sẽ được truyền vào qua tham số thứ hai.</p>

<p>Bạn cũng có thể sử dụng <code>create()</code> để vừa định nghĩa một phần tử của mô hình vừa lưu lại nó luôn. Hàm callback sẽ trả về một lỗi ứng với tham số thứ nhất và phần tử của mô hình vừa khởi tạo qua tham số thứ hai.</p>

<pre class="brush: js">SomeModel<code>.create({ name: 'also_awesome' }, function (err, awesome_instance) {
  if (err) return handleError(err);
  // lưu!
});</code></pre>

<p>Mỗi mô hình đều có một kết nối liên quan  (kết nối sẽ mặc định nếu dùng lệnh <code>mongoose.model()</code>). Bạn thêm mới một kết nối và gọi lệnh <code>.model()</code> để tạo thêm tài liệu trên một cơ sở dữ liệu khác.</p>

<p>Bạn có thể truy cập vào trường của bản ghi mới thông qua cú pháp chấm (.) , và thay đổi giá trị ở trong. Bạn sẽ phải gọi <code>save()</code> hoặc <code>update()</code> để lưu lại giá trị vừa chỉnh sửa vào cơ sở dữ liệu.</p>

<pre class="brush: js">// Truy cập vào trường dữ liệu của bản ghi qua cú pháp (.)
console.log(<code>awesome_instance.name</code>); //sẽ in ra '<code>also_awesome</code>'

// Thay đổi bản ghi bằng cách chỉnh sửa trường thông tin, sau đó gọi lệnh save().
<code>awesome_instance</code>.name="New cool name";
<code>awesome_instance.save(function (err) {
   if (err) return handleError(err); // lưu!
   });</code>
</pre>

<h4 id="Tìm_kiếm_các_bản_ghi">Tìm kiếm các bản ghi</h4>

<p>Bạn có thể tìm kiếm các bản ghi bằng các phương thức truy vấn, viết các câu truy vấn như đối với một tài liệu JSON. Đoạn code phía dưới trình bày cách tìm kiếm các vận động viên chơi ten-nít trong cơ sở dữ liệu, chỉ trả về các trường như <em>tên</em> và <em>tuổi </em>của vận động viên. GIờ ta sẽ chỉ xác định ra một trường (thể thao) bạn có thể thêm bao nhiêu tiêu chí tuỳ ý, xác định thêm các tiêu chí với regex, hoặc loại bỏ tất cả các điều kiện để trả về tất cả các vận động viên.</p>

<pre class="brush: js"><code>var Athlete = mongoose.model('Athlete', yourSchema);

// tìm tất cả các vận động viên chơi tennis, chọn hai trường 'name' và 'age'
Athlete.find({ 'sport': 'Tennis' }, 'name age', function (err, athletes) {
  if (err) return handleError(err);
  // 'athletes' chứa danh sách các vận động viên phù hợp với tiêu chí đã đề ra.
})</code></pre>

<p>Nếu bạn ném vào môt hàm callback, như ở trên, câu truy vấn sẽ được thực thi ngay lập tức. Hàm callback sẽ được gọi khi câu truy vấn hoàn tất.</p>

<div class="note">
<p><strong>Lưu ý:</strong> Tất cả các hàm callback trong Mongoose sử dụng mẫu <code>callback(error, result)</code>. Nếu có lỗi xảy ra khi thực hiện câu truy vấn, tham số <code>error</code> sẽ chứa tất cả các lỗi, và <code>result</code> sẽ trở thành null. Nếu câu truy vấn hợp lệ, tham số <code>error</code> sẽ trở thành null, và <code>result</code> sẽ chứa đựng kết quả của câu truy vấn.</p>
</div>

<p>Nếu bạn không truyền vào một hàm callback nào thì API sẽ trả về một giá trị kiểu <a href="http://mongoosejs.com/docs/api.html#query-js">Query</a>. Bạn có thể sử dụng đối tượng query này để kéo dài câu truy vấn trước khi thực thi nó (thông qua việc truyền vào một hàm callback) sau sử dụng phương thức <code>exec()</code>.</p>

<pre class="brush: js"><code>// tìm kiếm tất cả các vận động viên
var query = Athlete.find({ 'sport': 'Tennis' });

// chọn ra hai trường 'name' và 'age'
query.select('name age');

// giới hạn kết quả lại 5 bản ghi
query.limit(5);

// sắp xếp theo tên
query.sort({ age: -1 });

// thực thi câu truy vấn
query.exec(function (err, athletes) {
  if (err) return handleError(err);
  // athletes chứa một danh sách 5 vận động viên chơi tennis được xếp theo tên
})</code></pre>

<p>Ở trên ta đưa tất cả điều kiện truy vấn vào trong phương thức <code>find()</code>. Thay vì vậy ta cũng có thể sử dụng hàm <code>where()</code>, và ta có thể xâu chuỗi các lời gọi bằng cú pháp chấm (.) thay vì phải gọi từng câu riêng rẽ. Đoạn code phía dưới làm y chang phần trên, thêm vài điều kiện cho trường tuổi.</p>

<pre><code>Athlete.
  find().
  where('sport').equals('Tennis').
  where('age').gt(17).lt(50).  //Điều kiện thêm vào sau hàm where
  limit(5).
  sort({ age: -1 }).
  select('name age').
  exec(callback); // callback ở đây là tên hàm callback của ta.</code></pre>

<p>Phương thức <a href="http://mongoosejs.com/docs/api.html#query_Query-find">find()</a> lấy ra tất cả các bản ghi thoả mãn điều kiện, nhưng thường thì bạn chỉ muốn lấy ra một thôi. Các phương thức truy vấn phía dưới chỉ lấy ra một bản ghi:</p>

<ul>
 <li><code><a href="http://mongoosejs.com/docs/api.html#model_Model.findById">findById()</a></code>: Tìm kiếm tài liệu theo <code>id</code> (mỗi tài liệu có một <code>id</code> duy nhất).</li>
 <li><code><a href="http://mongoosejs.com/docs/api.html#query_Query-findOne">findOne()</a></code>: Tìm kiếm một tài liệu dựa theo tiêu chí đặt vào.</li>
 <li><code><a href="http://mongoosejs.com/docs/api.html#model_Model.findByIdAndRemove">findByIdAndRemove()</a></code>, <code><a href="http://mongoosejs.com/docs/api.html#model_Model.findByIdAndUpdate">findByIdAndUpdate()</a></code>, <code><a href="http://mongoosejs.com/docs/api.html#query_Query-findOneAndRemove">findOneAndRemove()</a></code>, <code><a href="http://mongoosejs.com/docs/api.html#query_Query-findOneAndUpdate">findOneAndUpdate()</a></code>: Tìm kiếm một tài liệu theo <code>id</code> hoặc theo tiêu chí và sửa hoặc xoá nó. Đây là các mẫu có ích khi cần phải tìm kiếm và chỉnh sửa.</li>
</ul>

<div class="note">
<p><strong>Lưu ý:</strong> Còn có phương thức <code><a href="http://mongoosejs.com/docs/api.html#model_Model.count">count()</a></code> để đếm số lượng bản ghi phù hợp với điều kiện đề ra. Cái này sẽ có ích nếu bạn chỉ cần tìm ra số lượng thay vì phải kéo về tất cả các bản ghi.</p>
</div>

<p>Còn có nhiều thứ nữa mà bạn có thể làm với các câu truy vấn. Để biết thêm thông tin mời xem: <a href="http://mongoosejs.com/docs/queries.html">Queries</a> (tài liệu Mongoose).</p>

<h4 id="Làm_việc_với_tài_liệu_liên_quan_—_sự_cư_ngụ">Làm việc với tài liệu liên quan — sự cư ngụ</h4>

<p>Bạn có thể thêm mới mối liên quan giữa các tài liệu/phần tử của mô hình qua trường schema <code>ObjectId</code>, hoặc từ một tài liệu đến nhiều qua một mảng <code>ObjectIds</code>. Trường này lưu trữ id của mô hình liên quan. Nếu bạn muốn lấy nội dung của tài liệu liên quan, bạn có thể sử dụng phương thức <code><a href="http://mongoosejs.com/docs/api.html#query_Query-populate">populate()</a></code> trong câu truy vấn để thay thế id với đống dữ liệu tương ứng.</p>

<p>Chẳng hạn, schema sau đây định nghĩa tác giả và tác phẩm. Mỗi tác giả có thể có nhiều tác phẩm, nên ta sử dụng một mảng đối tượng <code>ObjectId</code>. Mỗi tác phẩm có thể có một tác giả. Thuộc tính "ref" (được in đậm) kể cho ta biết schema nào mà model có thể gắn vào được.</p>

<pre class="brush: js"><code>var mongoose = require('mongoose')
  , Schema = mongoose.Schema

var authorSchema = Schema({
  name    : String,
  stories : [{ type: Schema.Types.ObjectId, <strong>ref</strong>: 'Story' }]
});

var storySchema = Schema({
  author : { type: Schema.Types.ObjectId, <strong>ref</strong>: 'Author' },
  title    : String
});

var Story  = mongoose.model('Story', storySchema);
var Author = mongoose.model('Author', authorSchema);</code></pre>

<p>Ta có thể lưu lại đống liên quan đến tài liệu tương ứng bằng cách gán cho nó giá trị <code>_id</code>. Đoạn bên dưới ta tạo ra một tác giả, rồi một cuốn sách và gắn id của tác giả vào trường tác giả của tác phẩm.</p>

<pre class="brush: js"><code>var bob = new Author({ name: 'Bob Smith' });

bob.save(function (err) {
  if (err) return handleError(err);

  //Bob giờ đã tồn tại, đến lúc tạo tác phẩm rồi
  var story = new Story({
    title: "Bob goes sledding",
    author: bob._id    // gắn _id của tác giả Bob. ID này được tạo ra mặc định!
  });

  story.save(function (err) {
    if (err) return handleError(err);
    // Bob giờ đã có tác phẩm của mình
  });
});</code></pre>

<p>Tài liệu tác phẩm của ta giờ được nối với trường tác giả thông qua ID của trong tài liệu tác giả. Để lấy thông tin của tác giả ta dùng hàm <code>populate()</code>, như bên dưới.</p>

<pre class="brush: js"><code>Story
.findOne({ title: 'Bob goes sledding' })
.populate('author') //Thay thế ID của tác giả bằng thông tin của tác giả!
.exec(function (err, story) {
  if (err) return handleError(err);
  console.log('The author is %s', story.author.name);
  // in ra "The author is Bob Smith"
});</code></pre>

<div class="note">
<p><strong>Lưu ý:</strong> Ta vừa thêm tác giả vào tác phẩm, nhưng lại không hề thêm tác phẩm vào mảng <code>stories</code> của tác giả. Thế thì làm thế nào để lấy ra tất cả tác phẩm của một tác giả nào đó? Có một cách là thêm tác giả vào mảng tác phẩm, nhưng như thế sẽ thành ra phân vị trí các thành phần trong khi ta cần giữ cho mối liên hệ giữa tác giả với tác phẩm được bảo toàn.</p>

<p>Cách tốt hơn là lấy <code>_id</code> của <em>tác giả</em>, rồi dùng <code>find()</code> để tìm trong trường tác giả xuyên suốt tác phẩm.</p>

<pre class="brush: js"><code>Story
.find({ author : bob._id })
.exec(function (err, stories) {
  if (err) return handleError(err);
  // trả về tất cả các tác phẩm có id của Bob.
});</code>
</pre>
</div>

<p>Đến đây là đã đủ hết mọi thứ bạn cần biết trong<em> bài viết này rồi</em>. Để biết thêm thông tin chi tiết, mời bạn tham khảo <a href="http://mongoosejs.com/docs/populate.html">Population</a> (tài liệu của Mongoose).</p>

<h3 id="Một_schemamô_hình_trên_một_tập_tin">Một schema/mô hình trên một tập tin</h3>

<p>Dù bạn có thể tạo ra schema và mô hình theo bất cứ kiến trúc nào bạn thích, nhưng chúng tôi vẫn khuyến nghị nên định nghĩa chúng trên mỗi mô đun riêng rẽ (tập tin), rồi xuất mô hình ra ngoài. Làm như thế này này:</p>

<pre class="brush: js"><code>// Tập tin: ./models/somemodel.js

//Nhập Mongoose
var mongoose = require('mongoose');

//Định nghĩa một schema
var Schema = mongoose.Schema;

var SomeModelSchema = new Schema({
    a_string          : String,
    a_date            : Date,
});

<strong>//Xuất ra lớp mô hình "SomeModel"
module.exports = mongoose.model('SomeModel', SomeModelSchema );</strong></code></pre>

<p>Sau đó bạn có thể nhập và sử dụng mô hình ngay tắp lự ở bất cứ đâu. Dưới đây là cách bạn lấy ra mọi phần tử của mô hình.</p>

<pre class="brush: js"><code>//Thê mới mô hình SomeModel thông qua lệnh require
var SomeModel = require('../models/somemodel')

// Sử dụng đối tượng SomeModel để tìm tất cả bản ghi của SomeModel
SomeModel.find(callback_function);</code></pre>

<h2 id="Thiết_lập_cơ_sở_dữ_liệu_MongoDB">Thiết lập cơ sở dữ liệu MongoDB</h2>

<p>Giờ khi đã hiểu những gì Mongoose có thể làm và cách để ta có thể thiết kế một cơ sở dữ liệu, đến lúc thực hành trên trang web <em>LocalLibrary</em> rồi. Bước đầu tiên trong bài thực hành là tạo mới một cơ sở dữ liệu MongoDb để lưu trữ dữ liệu cho thư viện của chúng ta.</p>

<p>Trong bài viết này ta sẽ sử dụng cơ sở dữ liệu nền điện toán đám mây của <a href="https://mlab.com/welcome/">mLab</a> (chọn kiểu "<a href="https://mlab.com/plans/pricing/">sandbox</a>" để dùng miễn phí). Cơ sở dữ liệu kiểu này không phù hợp dành cho các trang web thật vì nó không có dư thừa dữ liệu, nhưng lại rất hợp dành cho việc phát triển và xây dựng mẫu vật. Và bởi nó dễ dùng cũng như dễ thiết lập, và đừng quên rằng mLab là một trong những bên cung cấp khá nổi tiếng <em>cơ sở dữ liệu cung cấp dưới dạng dịch vụ</em> mà bạn có thể sẽ dùng cho dự án thật (vào thời điểm viết bài này bạn cũng có thể chọn các nhà cung cấp như <a href="https://www.compose.com/">Compose</a>, <a href="https://scalegrid.io/pricing.html">ScaleGrid</a> và <a href="https://www.mongodb.com/cloud/atlas">MongoDB Atlas</a>).</p>

<div class="note">
<p><strong>Lưu ý:</strong> Nếu bạn muốn thiết lập cơ sở dữ liệu MongoDb cục bộ thì hãy tìm và tải xuống <a href="https://www.mongodb.com/download-center">bản phù hợp với hệ thống của mình</a>. Phần còn lại khá là đơn giản, trừ phần URL của cơ sở dữ liệu mà bạn sẽ phải xác định nếu muốn kết nối tới.</p>
</div>

<p>Trước hết bạn cần <a href="https://mlab.com/signup/">tạo tài khoản</a> mLab (miễn phí, chỉ cần điền mẫu đăng ký là xong). </p>

<p>Sau khi đã đăng nhập vào, bạn sẽ được chuyển tới <a href="https://mlab.com/home">trang chủ</a>:</p>

<ol>
 <li>Nhấn <strong>Create New</strong> trong phần <em>MongoDB Deployments</em>.<img alt="" src="https://mdn.mozillademos.org/files/14446/mLabCreateNewDeployment.png" style="height: 415px; width: 1000px;"></li>
 <li>Nó sẽ mở ra màn hình <em>Cloud Provider Selection</em>.<br>
  <img alt="MLab - screen for new deployment" src="https://mdn.mozillademos.org/files/15661/mLab_new_deployment_form_v2.png" style="height: 931px; width: 1297px;"><br>

  <ul>
   <li>Chọn SANDBOX (Free) plan trong phần Plan Type. </li>
   <li>Chọn bất cứ nhà cung cấp nào trong phần <em>Cloud Provider</em>. Mỗi nhà cung cấp khác nhau ở các vùng lãnh thổ địa lý khác nhau (ở ngay dưới phần selected plan type).</li>
   <li>Bấm nút <strong>Continue</strong>.</li>
  </ul>
 </li>
 <li>Màn hình <em>Select Region</em> mở ra.
  <p><img alt="Select new region screen" src="https://mdn.mozillademos.org/files/15662/mLab_new_deployment_select_region_v2.png" style="height: 570px; width: 1293px;"></p>

  <ul>
   <li>
    <p>Chọn vùng lãnh thổ gần bạn nhất rồi nhấn <strong>Continue</strong>.</p>
   </li>
  </ul>
 </li>
 <li>
  <p>Màn hình <em>Final Details</em> mở ra.<br>
   <img alt="New deployment database name" src="https://mdn.mozillademos.org/files/15663/mLab_new_deployment_final_details.png" style="height: 569px; width: 1293px;"></p>

  <ul>
   <li>
    <p>Nhập tên cho cơ sở dữ liệu vừa tạo ra như <code>local_library</code> và chọn <strong>Continue</strong>.</p>
   </li>
  </ul>
 </li>
 <li>
  <p>Màn hình <em>Order Confirmation</em> sẽ mở lên.<br>
   <img alt="Order confirmation screen" src="https://mdn.mozillademos.org/files/15664/mLab_new_deployment_order_confirmation.png" style="height: 687px; width: 1290px;"></p>

  <ul>
   <li>
    <p>Nhấn <strong>Submit Order</strong> để tạo mới cơ sở dữ liệu.</p>
   </li>
  </ul>
 </li>
 <li>
  <p>Bạn sẽ được điều hướng về trang chủ. Nhấn vào cơ sở dữ liệu vừa thêm mới để xem chi tiết. Như bạn thấy đấy, cơ sở dữ liệu không có bất cứ bộ sưu tập nào (dữ liệu).<br>
   <img alt="mLab - Database details screen" src="https://mdn.mozillademos.org/files/15665/mLab_new_deployment_database_details.png" style="height: 700px; width: 1398px;"><br>
    <br>
   URL mà bạn cần ở ngay đầu trang (trong phần khoanh đỏ). Để dùng được nó bạn phải tạo ra người dùng mới.</p>
 </li>
 <li>Nhấn vào tab <strong>Users</strong> và bấm nút <strong>Add database user</strong>.</li>
 <li>Điền tên đăng nhập và mật khẩu (hai lần), và nhấn nút <strong>Create</strong>. Đừng bao giờ chọn <em>Make read only</em>.<br>
  <img alt="" src="https://mdn.mozillademos.org/files/14454/mLab_database_users.png" style="height: 204px; width: 600px;"></li>
</ol>

<p>Giờ đã có cơ sở dữ liệu rồi, và cả URL (với tên đăng nhập và mật khẩu) đã sẵn sàng để truy xuất. Trông nó sẽ như thế này: <code>mongodb://your_user_namer:your_password@ds119748.mlab.com:19748/local_library</code>.</p>

<h2 id="Cài_đặt_Mongoose">Cài đặt Mongoose</h2>

<p>Mở command prompt và chuyển tới thư mục chứa <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/skeleton_website">trang web Local Library</a>. Nhập lệnh dưới để cài đặt Mongoose (và đống dependency của nó) và nó sẽ tự động thêm vào <strong>package.json</strong> của bạn, nếu bạn đã làm như với <a href="#Installing_Mongoose_and_MongoDB">Mongoose Primer</a> ở trên.</p>

<pre class="brush: bash">npm install mongoose --save
</pre>

<h2 id="Kết_nối_tới_MongoDB">Kết nối tới MongoDB</h2>

<p>Mở <strong>/app.js</strong> (trong project của bạn) và sao chép đống phía dưới để khai báo<em> đối tượng ứng dụng Express</em> (sau dòng <code>var app = express();</code>). Thay thế url của cơ sở dữ liệu ('<em>insert_your_database_url_here</em>') bằng URL của mình (như cái vừa tạo ra bằng<a href="#Setting_up_the_MongoDB_database"> mLab</a>).</p>

<pre class="brush: js">//Thiết lập kết nối tới Mongoose
var mongoose = require('mongoose');
var mongoDB = '<em>insert_your_database_url_here</em>';
mongoose.connect(mongoDB);
mongoose.Promise = global.Promise;
var db = mongoose.connection;
db.on('error', console.error.bind(console, 'MongoDB connection error:'));</pre>

<p>Như đã nói <a href="#Connecting_to_MongoDB">trong phần Mongoose primer phía trên</a>, đoạn code này tạo ra kết nối mặc định tới cơ sở dữ liệu và ràng buộc sự kiện lỗi (để in lỗi ra console). </p>

<h2 id="Định_nghĩa_Schema_cho_LocalLibrary">Định nghĩa Schema cho LocalLibrary</h2>

<p>Ta sẽ tạo ra mô đun cho từng mô hình, như <a href="#One_schemamodel_per_file">đã đề cập phía trên</a>. Bắt đầu bằng cách thêm mới thư mục trong thư mục gốc (<strong>/models</strong>) và tạo từng tập tin cho mỗi mô hình:</p>

<pre>/express-locallibrary-tutorial  //the project root
  <strong>/models</strong>
    <strong>author.js</strong>
    <strong>book.js</strong>
    <strong>bookinstance.js</strong>
    <strong>genre.js</strong>
</pre>

<h3 id="Mô_hình_tác_giả">Mô hình tác giả</h3>

<p>Sao chép đoạn code schema <code>Author</code> code bên dưới và dán vào tập tin <strong>./models/author.js</strong>. Scheme định nghĩa rằng một tác giả có kiểu <code>String</code> SchemaTypes cho hai trường họ và tên , bắt buộc và có giới hạn nhiều nhất 100 ký tự, và kiểu <code>Date</code> cho trường ngày sinh và ngày mất.</p>

<pre class="brush: js">var mongoose = require('mongoose');

var Schema = mongoose.Schema;

var AuthorSchema = new Schema(
  {
    first_name: {type: String, required: true, max: 100},
    family_name: {type: String, required: true, max: 100},
    date_of_birth: {type: Date},
    date_of_death: {type: Date},
  }
);

<strong>// Tạo phương thức ảo cho tên đầy đủ
AuthorSchema
.virtual('name')
.get(function () {
  return this.family_name + ', ' + this.first_name;
});</strong>

// Phương thức ảo cho URL của tác giả
AuthorSchema
.virtual('url')
.get(function () {
  return '/catalog/author/' + this._id;
});

//xuất mô hình
module.exports = mongoose.model('Author', AuthorSchema);

</pre>

<p>Ta vừa khai báo <a href="#Virtual_properties">phần ảo</a> cho AuthorSchema với tên là "url" trả về URL tuyệt đối bắt buộc để lấy ra phần tử nhất định của mô hình — ta sẽ dùng thuộc tính này trong mẫu mỗi khi cần lấy ra đường dẫn tới tác giả.</p>

<div class="note">
<p><strong>Lưu ý:</strong> Khai báo URL bằng hàm ảo trong schema là ý tưởng tốt bởi vì URL sẽ chỉ cần thay đổi tại một nơi.<br>
 Vào lúc này các URL sẽ không thể hoạt động, ta chưa đặt ra các route nào để dẫn lối cho từng phần tử của mô hình. Ta sẽ làm việc này trong các bài viết sau!</p>
</div>

<p>Sau khi đã xong thì ta xuất mô hình ra thôi.</p>

<h3 id="Mô_hình_sách">Mô hình sách</h3>

<p>Sao chép đoạn code schema <code>Book</code> bên dưới và dán nó vào tập tin <strong>./models/book.js</strong>. Làm tương tự đối như với tác giả — ta vừa khai báo một schema có nhiều trường String và một phần ảo để lấy URL của các bản ghi sách, sau đó thì xuất nó ra.</p>

<pre class="brush: js">var mongoose = require('mongoose');

var Schema = mongoose.Schema;

var BookSchema = new Schema(
  {
    title: {type: String, required: true},
  <strong>  author: {type: Schema.ObjectId, ref: 'Author', required: true},</strong>
    summary: {type: String, required: true},
    isbn: {type: String, required: true},
  <strong>  genre: [{type: Schema.ObjectId, ref: 'Genre'}]</strong>
  }
);

// Tạo hàm ảo lấy URL của sách
BookSchema
.virtual('url')
.get(function () {
  return '/catalog/book/' + this._id;
});

//Xuất mô hình
module.exports = mongoose.model('Book', BookSchema);
</pre>

<p>Sự khác biệt chính là ta vừa tạo ra hai mối liên quan đến sách:</p>

<ul>
 <li>author được trỏ tới mô hình đối tượng <code>Author</code>, và bắt buộc.</li>
 <li>genre được trỏ tới một mảng mô hình đối tượng <code>Genre</code>. Ta vẫn chưa định nghĩa đối tượng này!</li>
</ul>

<h3 id="Mô_hình_BookInstance">Mô hình BookInstance</h3>

<p>Cuối cùng sao chép đoạn code schema <code>BookInstance</code> bên dưới và dán nó vào tập tin <strong>./models/bookinstance.js</strong><code>BookInstance</code> đại diện cho số bản sách mà ai đó mượn, và bao gồm thông itn về thời điểm sách  về hoặc hạn trả sách dự kiến, "đánh dấu" hoặc lấy chi tiết phiên bản.</p>

<pre class="brush: js">var mongoose = require('mongoose');

var Schema = mongoose.Schema;

var BookInstanceSchema = new Schema(
  {
    book: { type: Schema.ObjectId, ref: 'Book', required: true }, //reference to the associated book
    imprint: {type: String, required: true},
    status: {type: String, required: true, <strong>enum: ['Available', 'Maintenance', 'Loaned', 'Reserved']</strong>, <strong>default: 'Maintenance'</strong>},
    due_back: {type: Date, <strong>default: Date.now</strong>}
  }
);

// Lấy ra URL của bookinstance
BookInstanceSchema
.virtual('url')
.get(function () {
  return '/catalog/bookinstance/' + this._id;
});

//Xuất mô hình
module.exports = mongoose.model('BookInstance', BookInstanceSchema);</pre>

<p>Các thuộc tính mới được thêm vào trong này nằm trong phần trường dữ liệu:</p>

<ul>
 <li><code>enum</code>: Cho phép ta đặt giá trị chấp nhận được cho xâu truyền vào. Trong trường hợp này ta dùng nó để xác định trạng thái còn sẵn của sách (sử dụng enum sẽ giúp ta tránh khỏi các lỗi chính tả hoặc khai khống giá trị cho trạng thái)</li>
 <li><code>default</code>: Ta dùng default để đặt giá trị mặc định cho những bookinstances mới khởi tạo để bảo trì <code>due_back</code> mặc định <code>now</code> (lưu ý cách gọi hàm Date khi thiết lập ngày giờ!)</li>
</ul>

<p>Những schema còn lại làm tương tự.</p>

<h3 id="Mô_hình_thể_loại_-_thử_thách!">Mô hình thể loại - thử thách!</h3>

<p>Mở tập tin <strong>./models/genre.js</strong> của bạn lên và tạo mới một schema để lưu lại thể loại sách (các kiểu sách như là truyện tiểu thuyết, tư liệu lịch sử...).</p>

<p>Cách định nghĩa cũng giống như các mô hình ở trên:</p>

<ul>
 <li>Mô hình nên có một <code>String</code> SchemaType tên là <code>name</code> để mô tả thể loại.</li>
 <li>Tên này phải bắt buộc và có từ 3 đến 100 kí tự.</li>
 <li>Tạo một <a href="#Virtual_properties">phương thức ảo</a> cho URL của thể loại, để tên là <code>url</code>.</li>
 <li>Xuất mô hình.</li>
</ul>

<h2 id="Kiểm_thử_—_tạo_ra_vài_bản_ghi">Kiểm thử — tạo ra vài bản ghi</h2>

<p>Xong xuôi rồi đó. Giờ ta đã có tất cả mô hình!</p>

<p>Để có thể kiểm thử mô hình (và để tạo ra vài sách mẫu và một số thứ khác ta sẽ dùng trong bài viết sau) ta sẽ chạy một đoạn kịch bản <em>independent</em> để tạo ra các bản ghi cho từng mô hình:</p>

<ol>
 <li>Tải về (hoặc tạo mới) tập tin <a href="https://raw.githubusercontent.com/hamishwillee/express-locallibrary-tutorial/master/populatedb.js">populatedb.js</a> trong thư mục <em>express-locallibrary-tutorial</em> (đồng cấp với <code>package.json</code>).

  <div class="note">
  <p><strong>Lưu ý:</strong> Bạn không cần hiểu cách thức <a href="https://raw.githubusercontent.com/hamishwillee/express-locallibrary-tutorial/master/populatedb.js">populatedb.js</a> hoạt động; nó chỉ thêm dữ liệu mẫu vào trong cơ sở dữ liệu thôi.</p>
  </div>
 </li>
 <li>Nhập lệnh phía dưới vào trong thư mục chứa project để cài đặt mô-đun <em>async</em> để có thể chạy được đoạn kịch bản (ta sẽ bàn về việc này trong bài tiếp theo, )
  <pre class="brush: bash">npm install async --save</pre>
 </li>
 <li>Chạy đoạn kịch bản bằng node trong command prompt của bạn, truyền vào URL của cơ sở dữ liệu MongoDB (như cái bạn đã thay thế cho <em>insert_your_database_url_here</em>, trong <code>app.js</code> phía trên):
  <pre class="brush: bash">node populatedb &lt;your mongodb url&gt;​​​​</pre>
 </li>
 <li>Đoạn code sẽ chạy thành công và in ra những vật được tạo ra trên màn console.</li>
</ol>

<div class="note">
<p><strong>Mẹo:</strong> Lên cơ sở dữ liệu của bạn trên <a href="https://mlab.com/home">mLab</a>. Giờ bạn có thể chui vào bộ sưu tập Books, Authors, Genres và BookInstances, và kiểm tra các tài liệu vừa được tạo.</p>
</div>

<h2 id="Tóm_lại">Tóm lại</h2>

<p>Trong bài viết này ta học một chút về cơ sở dữ liệu và ORMs trên Node/Express, và cách để định nghĩa schema và mô hình của Mongoose. Sau đó ta đã thực hành thiết kế và triển khai các mô hình <code>Book</code>, <code>BookInstance</code>, <code>Author</code> và <code>Genre</code> cho trang web <em>LocalLibrary</em>.</p>

<p>Sau cùng ta kiểm thử những gì vừa viết ra bằng cách tạo một đống các phần tử (bằng cách sử dụng một đoạn mã kịch bản). Trong bài tiếp theo ta sẽ học cách tạo các trang để trình bày các thứ.</p>

<h2 id="Đọc_thêm">Đọc thêm</h2>

<ul>
 <li><a href="https://expressjs.com/en/guide/database-integration.html">Database integration</a> (tài liệu của Express)</li>
 <li><a href="http://mongoosejs.com/">Mongoose website</a> (tài liệu của Mongoose)</li>
 <li><a href="http://mongoosejs.com/docs/guide.html">Mongoose Guide</a> (tài liệu của Mongoose)</li>
 <li><a href="http://mongoosejs.com/docs/validation.html">Validation</a> (tài liệu của Mongoose)</li>
 <li><a href="http://mongoosejs.com/docs/schematypes.html">Schema Types</a> (tài liệu của Mongoose)</li>
 <li><a href="http://mongoosejs.com/docs/models.html">Models</a> (tài liệu của Mongoose)</li>
 <li><a href="http://mongoosejs.com/docs/queries.html">Queries</a> (tài liệu của Mongoose)</li>
 <li><a href="http://mongoosejs.com/docs/populate.html">Population</a> (tài liệu của Mongoose)</li>
</ul>

<p>{{PreviousMenuNext("Learn/Server-side/Express_Nodejs/skeleton_website", "Learn/Server-side/Express_Nodejs/routes", "Learn/Server-side/Express_Nodejs")}}</p>

<p> </p>

<h2 id="In_this_module">In this module</h2>

<ul>
 <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Introduction">Express/Node introduction</a></li>
 <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/development_environment">Setting up a Node (Express) development environment</a></li>
 <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Tutorial_local_library_website">Express Tutorial: The Local Library website</a></li>
 <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/skeleton_website">Express Tutorial Part 2: Creating a skeleton website</a></li>
 <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/mongoose">Express Tutorial Part 3: Using a Database (with Mongoose)</a></li>
 <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/routes">Express Tutorial Part 4: Routes and controllers</a></li>
 <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express Tutorial Part 5: Displaying library data</a></li>
 <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/forms">Express Tutorial Part 6: Working with forms</a></li>
 <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/deployment">Express Tutorial Part 7: Deploying to production</a></li>
</ul>

<p> </p>