UNPKG

16.5 kBMarkdownView Raw
1# LoopBack Definition Language Guide
2
3LoopBack Definition Language (LDL) is simple DSL to define data models in
4JavaScript or plain JSON. With LoopBack, we often start with a model definition
5which describes the structure and types of data. The model establishes common
6knowledge of data in LoopBack.
7
8## Describing a simple model
9
10Let's start with a simple example in plain JSON.
11
12 {
13 "id": "number",
14 "firstName": "string",
15 "lastName": "string"
16 }
17
18The model simply defines a `user` model that consists of three properties:
19
20* id - The user id. It's a number.
21* firstName - The first name. It's a string.
22* lastName - The last name. It's a string.
23
24Each key in the JSON object defines a property in our model which will be cast
25to its associated type. The simplest form of a property definition is
26`propertyName: type`. The key is the name of the property and the value is the
27type of the property. We'll cover more advanced form later in this guide.
28
29LDL supports a list of built-in types, including the basic types from JSON:
30
31* String
32* Number
33* Boolean
34* Array
35* Object
36
37**Note**: The type name is case-insensitive, i.e., either "Number" or "number"
38can be used.
39
40The same model can also be described in JavaScript code:
41
42 var UserDefinition = {
43 id: Number,
44 firstName: String,
45 lastName: String
46 }
47
48As we can see, the JavaScript version is less verbose as it doesn't require
49quotes for property names. The types are described using JavaScript constructors,
50for example, `Number` for `"Number"`. String literals are also supported.
51
52Now we have the definition of a model, how do we use it in LoopBack Node.js
53code? It's easy, LoopBack will build a JavaScript constructor (or class) for you.
54
55## Creating a model constructor
56
57LDL compiles the model definition into a JavaScript constructor using
58`ModelBuilder.define` APIs. ModelBuilder is the basic factory to create model
59constructors.
60
61ModelBuilder.define() method takes the following arguments:
62
63- name: The model name
64- properties: An object of property definitions
65- options: An object of options, optional
66
67Here is an example,
68
69 var ModelBuilder = require('loopback-datasource-juggler').ModelBuilder;
70
71 // Create an instance of the ModelBuilder
72 var modelBuilder = new ModelBuilder();
73
74 // Describe the user model
75 var UserDefinition = {
76 id: Number,
77 firstName: String,
78 lastName: String
79 }
80
81 // Compile the user model definition into a JavaScript constructor
82 var User = modelBuilder.define('User', UserDefinition);
83
84 // Create a new instance of User
85 var user = new User({id: 1, firstName: 'John', lastName: 'Smith'});
86
87 console.log(user.id); // 1
88 console.log(user.firstName); // 'John'
89 console.log(user.lastName); // 'Smith'
90
91
92That's it. Now you have a User constructor representing the user model.
93
94At this point, the constructor only has a set of accessors to model properties.
95No behaviors have been introduced yet.
96
97## Adding logic to a model
98
99Models describe the shape of data. To leverage the data, we'll add logic to the
100model for various purposes, such as:
101
102- Interact with the data store for CRUD
103- Add behavior around a model instance
104- Add service operations using the model as the context
105
106There are a few ways to add methods to a model constructor:
107
108### Create the model constructor from a data source
109A LoopBack data source injects methods on the model.
110
111
112 var DataSource = require('loopback-datasource-juggler').DataSource;
113 var ds = new DataSource('memory');
114
115 // Compile the user model definition into a JavaScript constructor
116 var User = ds.define('User', UserDefinition);
117
118 // Create a new instance of User
119 User.create({id: 1, firstName: 'John', lastName: 'Smith'}, function(err, user) {
120 console.log(user); // The newly created user instance
121 User.findById(1, function(err, user) {
122 console.log(user); // The user instance for id 1
123 user.firstName = 'John1'; // Change the property
124 user.save(function(err, user) {
125 console.log(user); // The modified user instance for id 1
126 });
127 };
128 });
129
130
131### Attach the model to a data source
132A plain model constructor created from `ModelBuilder` can be attached a `DataSource`.
133
134
135 var DataSource = require('loopback-datasource-juggler').DataSource;
136 var ds = new DataSource('memory');
137
138 User.attachTo(ds); // The CRUD methods will be mixed into the User constructor
139
140### Manually add methods to the model constructor
141Static methods can be added by declaring a function as a member of the model
142constructor. Within a class method, other class methods can be called using the
143model as usual.
144
145
146 // Define a static method
147 User.findByLastName = function(lastName, cb) {
148 User.find({where: {lastName: lastName}, cb);
149 };
150
151 User.findByLastName('Smith', function(err, users) {
152 console.log(users); // Print an array of user instances
153 });
154
155Instance methods can be added to the prototype. Within instance methods, the
156model instance itself can be referenced with this keyword.
157
158 // Define a prototype method
159 User.prototype.getFullName = function () {
160 return this.firstName + ' ' + this.lastName;
161 };
162
163 var user = new User({id: 1, firstName: 'John', lastName: 'Smith'});
164 console.log(user.getFullName()); // 'John Smith'
165
166
167## Exploring advanced LDL features
168
169As we mentioned before, a complete model definition is an object with three
170properties:
171
172- name: The model name
173- options: An object of options, optional
174- properties: An object of property definitions
175
176### Model level options
177There are a set of options to control the model definition.
178
179- strict:
180 - true: Only properties defined in the model are accepted. Use this
181 mode if you want to make sure only predefined properties are accepted. Relational databases only support this setting.
182 - false: The model will be an open model. All properties are accepted,
183 including the ones that not predefined with the model. This mode is useful
184 if the mobile application just wants to store free form JSON data to
185 a schema-less database such as MongoDB. For relational databases, the value will be converted back to true.
186 - undefined: Default to false unless the data source is backed by a
187 relational database such as Oracle or MySQL.
188
189
190- idInjection:
191 - true: An `id` property will be added to the model automatically
192 - false: No `id` property will be added to the model
193
194
195- plural: The plural form of the model name. If not present, it will be derived from the model name following English
196conventions.
197
198
199- Data source specific mappings
200The model can be decorated with connector-specific options to customize the
201mapping between the model and the connector. For example, we can define the
202corresponding schema/table names for Oracle as follows:
203
204 {
205 "name": "Location",
206 "options": {
207 "idInjection": false,
208 "oracle": {
209 "schema": "BLACKPOOL",
210 "table": "LOCATION"
211 }
212 },
213 ...
214 }
215
216### Property definitions
217A model consists of a list of properties. The basic example use
218`propertyName: type` to describe a property.
219
220Properties can have options in addition to the type. LDL uses a JSON object to
221describe such properties, for example:
222
223 "id": {"type": "number", "id": true, "doc": "User ID"}
224
225 "firstName": {"type": "string", "required": true, "oracle": {"column": "FIRST_NAME", "type": "VARCHAR(32)"}}
226
227**Note** `"id": "number"` is a short form of `"id": {"type": "number"}`.
228
229#### Data types
230LDL supports the following data types.
231
232 - String/Text
233 - Number
234 - Date
235 - Boolean
236 - Buffer/Binary
237 - Array
238 - Any/Object/JSON
239 - GeoPoint
240
241##### Array types
242LDL supports array types as follows:
243
244- `{emails: [String]}`
245- `{"emails": ["String"]}`
246- `{emails: [{type: String, length: 64}]}`
247
248##### Object types
249A model often has properties that consist of other properties. For example, the
250user model can have an `address` property
251that in turn has properties such as `street`, `city`, `state`, and `zipCode`.
252
253LDL allows inline declaration of such properties, for example,
254
255 var UserModel = {
256 firstName: String,
257 lastName: String,
258 address: {
259 street: String,
260 city: String,
261 state: String,
262 zipCode: String
263 },
264 ...
265 }
266
267The value of the address is the definition of the `address` type, which can be
268also considered as an anonymous model.
269
270If you intend to reuse the address model, we can define it independently and
271reference it in the user model. For example,
272
273 var AddressModel = {
274 street: String,
275 city: String,
276 state: String,
277 zipCode: String
278 };
279
280 var Address = ds.define('Address', AddressModel);
281
282 var UserModel = {
283 firstName: String,
284 lastName: String,
285 address: 'Address', // or address: Address
286 ...
287 }
288
289 var User = ds.define('User', UserModel);
290
291**Note**: The user model has to reference the Address constructor or the model
292name - `'Address'`.
293
294
295#### ID(s) for a model
296A model representing data to be persisted in a database usually has one or more
297properties as an id to uniquely identify the model instance. For example, the
298`user` model can have user ids.
299
300By default, if no id properties are defined and the `idInjection` of the model
301options is false, LDL will automatically add an id property to the model as follows:
302
303 id: {type: Number, generated: true, id: true}
304
305To explicitly specify a property as `id`, LDL provides an `id` property for the
306option. The value can be true, false, or a number.
307
308- true: It's an id
309- false or any falsey values: It's not an id (default)
310- a positive number, such as 1 or 2: It's the index of the composite id
311
312LDL supports the definition of a composite id that has more than one properties.
313For example,
314
315 var InventoryDefinition =
316 {
317 productId: {type: String, id: 1},
318 locationId: {type: String, id: 2},
319 qty: Number
320 }
321
322The composite id is (productId, locationId) for an inventory model.
323
324**Note: Composite ids are NOT supported as query parameters in REST APIs yet.**
325
326#### Property documentation
327* doc: Documentation of the property
328
329#### Constraints
330Constraints are modeled as options too, for example:
331
332* default: The default value of the property
333* required: Indicate if the property is required
334* pattern: A regular expression pattern that a string should match
335* min/max: The minimal and maximal value
336* length: The maximal length of a string
337
338
339#### Conversion and formatting
340Format conversions can also be declared as options, for example:
341
342* trim: Trim the string
343* lowercase: Convert the string to be lowercase
344* uppercase: Convert the string to be uppercase
345* format: Format a Date
346
347#### Mapping
348Data source specific mappings can be added to the property options, for example,
349to map a property to be a column in Oracle database table, you can use the
350following syntax:
351
352 "oracle": {"column": "FIRST_NAME", "type": "VARCHAR", "length": 32}
353
354
355### Relations between models
356
357#### belongsTo
358
359A `belongsTo` relation sets up a one-to-one connection with another model, such
360that each instance of the declaring model "belongs to" one instance of the other
361model. For example, if your application includes customers and orders, and each order
362can be placed by exactly one customer.
363
364![belongsTo](belongs-to.png "belongsTo")
365
366 var Order = ds.createModel('Order', {
367 customerId: Number,
368 orderDate: Date
369 });
370
371 var Customer = ds.createModel('Customer', {
372 name: String
373 });
374
375 Order.belongsTo(Customer);
376
377
378The code above basically says Order has a reference called `customer` to User using
379the `customerId` property of Order as the foreign key. Now we can access the customer
380in one of the following styles:
381
382
383 order.customer(callback); // Get the customer for the order
384 order.customer(); // Get the customer for the order synchronously
385 order.customer(customer); // Set the customer for the order
386
387
388#### hasMany
389
390A `hasMany` relation builds a one-to-many connection with another model. You'll
391often find this relation on the "other side" of a `belongsTo` relation. This
392relation indicates that each instance of the model has zero or more instances
393of another model. For example, in an application containing customers and orders, a
394customer has zero or more orders.
395
396![hasMany](has-many.png "hasMany")
397
398 var Order = ds.createModel('Order', {
399 customerId: Number,
400 orderDate: Date
401 });
402
403 var Customer = ds.createModel('Customer', {
404 name: String
405 });
406
407 Customer.hasMany(Order, {as: 'orders', foreignKey: 'customerId'});
408
409
410
411Scope methods created on the base model by hasMany allows to build, create and
412query instances of other class. For example,
413
414 customer.orders(filter, callback); // Find orders for the customer
415 customer.orders.build(data); // Build a new order
416 customer.orders.create(data, callback); // Create a new order for the customer
417 customer.orders.destroyAll(callback); // Remove all orders for the customer
418 customer.orders.findById(orderId, callback); // Find an order by id
419 customer.orders.destroy(orderId, callback); // Delete and order by id
420
421
422#### hasMany through
423
424A `hasMany through` relation is often used to set up a many-to-many connection with another model. This relation
425indicates that the declaring model can be matched with zero or more instances of another model by proceeding through
426a third model. For example, consider a medical practice where patients make appointments to see physicians. The
427relevant association declarations could look like this:
428
429![hasManyThrough](has-many-through.png "hasManyThrough")
430
431 var Physician = ds.createModel('Physician', {name: String});
432 var Patient = ds.createModel('Patient', {name: String});
433 var Appointment = ds.createModel('Appointment', {
434 physicianId: Number,
435 patientId: Number,
436 appointmentDate: Date
437 });
438
439 Appointment.belongsTo(Patient);
440 Appointment.belongsTo(Physician);
441
442 Physician.hasMany(Patient, {through: Appointment});
443 Patient.hasMany(Physician, {through: Appointment});
444
445Now the Physician model has a virtual property called `patients`:
446
447 physician.patients(filter, callback); // Find patients for the physician
448 physician.patients.build(data); // Build a new patient
449 physician.patients.create(data, callback); // Create a new patient for the physician
450 physician.patients.destroyAll(callback); // Remove all patients for the physician
451 physician.patients.add(patient, callback); // Add an patient to the physician
452 physician.patients.remove(patient, callback); // Remove an patient from the physician
453 physician.patients.findById(patientId, callback); // Find an patient by id
454
455
456#### hasAndBelongsToMany
457
458A `hasAndBelongsToMany` relation creates a direct many-to-many connection with
459another model, with no intervening model. For example, if your application
460includes users and groups, with each group having many users and each user
461appearing in many groups, you could declare the models this way,
462
463![hasAndBelongsToMany](has-and-belongs-to-many.png "hasAndBelongsToMany")
464
465 User.hasAndBelongsToMany('groups', {model: Group, foreignKey: 'groupId'});
466 user.groups(callback); // get groups of the user
467 user.groups.create(data, callback); // create a new group and connect it with the user
468 user.groups.add(group, callback); // connect an existing group with the user
469 user.groups.remove(group, callback); // remove the user from the group
470
471
472### Extend from a base model
473LDL allows a new model to extend from an existing model. For example, Customer
474can extend from User as follows. The Customer model will inherit properties and
475methods from the User model.
476
477 var Customer = User.extend('customer', {
478 accountId: String,
479 vip: Boolean
480 });
481
482### Mix in model definitions
483Some models share the common set of properties and logic around. LDL allows a
484model to mix in one or more other models. For example,
485
486 var TimeStamp = modelBuilder.define('TimeStamp', {created: Date, modified: Date});
487 var Group = modelBuilder.define('Group', {groups: [String]});
488 User.mixin(Group, TimeStamp);