├── .editorconfig ├── .github ├── ISSUE_TEMPLATE.md ├── PULL_REQUEST_TEMPLATE.md └── stale.yml ├── .gitignore ├── .jshintignore ├── .jshintrc ├── .npmignore ├── CODEOWNERS ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── client └── README.md ├── common └── models │ ├── account.js │ ├── account.json │ ├── address.js │ ├── address.json │ ├── author.json │ ├── book.json │ ├── customer.js │ ├── customer.json │ ├── email-address.js │ ├── email-address.json │ ├── link.json │ ├── order.js │ ├── order.json │ ├── reader.json │ ├── shipment.js │ └── shipment.json ├── package.json ├── server ├── boot │ ├── a-customer-nest-remoting.js │ ├── authentication.js │ ├── root.js │ ├── sample-customers.js │ ├── z-book-people.js │ ├── z-customer-accounts.js │ ├── z-customer-address.js │ └── z-customer-emails.js ├── component-config.json ├── config.json ├── datasources.json ├── middleware.json ├── model-config.json ├── server.js └── views │ ├── account.ejs │ ├── address.ejs │ ├── email.ejs │ └── index.ejs └── test ├── rest_api_test.js └── start-server.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # http://editorconfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | indent_style = space 9 | indent_size = 2 10 | end_of_line = lf 11 | charset = utf-8 12 | trim_trailing_whitespace = true 13 | insert_final_newline = true 14 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 9 | 10 | # Description/Steps to reproduce 11 | 12 | 16 | 17 | # Link to reproduction sandbox 18 | 19 | 24 | 25 | # Expected result 26 | 27 | 30 | 31 | # Additional information 32 | 33 | 38 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Description 2 | 3 | 4 | #### Related issues 5 | 6 | 12 | 13 | - connect to 14 | 15 | ### Checklist 16 | 17 | 22 | 23 | - [ ] New tests added or existing tests modified to cover all changes 24 | - [ ] Code conforms with the [style 25 | guide](http://loopback.io/doc/en/contrib/style-guide.html) 26 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Number of days of inactivity before an issue becomes stale 2 | daysUntilStale: 60 3 | # Number of days of inactivity before a stale issue is closed 4 | daysUntilClose: 14 5 | # Issues with these labels will never be considered stale 6 | exemptLabels: 7 | - pinned 8 | - security 9 | - critical 10 | - p1 11 | - major 12 | # Label to use when marking an issue as stale 13 | staleLabel: stale 14 | # Comment to post when marking an issue as stale. Set to `false` to disable 15 | markComment: > 16 | This issue has been automatically marked as stale because it has not had 17 | recent activity. It will be closed if no further activity occurs. Thank you 18 | for your contributions. 19 | # Comment to post when closing a stale issue. Set to `false` to disable 20 | closeComment: > 21 | This issue has been closed due to continued inactivity. Thank you for your understanding. 22 | If you believe this to be in error, please contact one of the code owners, 23 | listed in the `CODEOWNERS` file at the top-level of this repository. 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | -------------------------------------------------------------------------------- /.jshintignore: -------------------------------------------------------------------------------- 1 | /client/ 2 | /node_modules/ 3 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "esnext": true, 4 | "bitwise": true, 5 | "camelcase": true, 6 | "eqeqeq": true, 7 | "eqnull": true, 8 | "immed": true, 9 | "indent": 2, 10 | "latedef": "nofunc", 11 | "newcap": true, 12 | "nonew": true, 13 | "noarg": true, 14 | "quotmark": "single", 15 | "regexp": true, 16 | "undef": true, 17 | "unused": false, 18 | "trailing": true, 19 | "sub": true, 20 | "maxlen": 80, 21 | "globals" : { 22 | /* MOCHA */ 23 | "describe" : false, 24 | "it" : false, 25 | "before" : false, 26 | "after" : false 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .project 3 | *.sublime-* 4 | .DS_Store 5 | *.seed 6 | *.log 7 | *.csv 8 | *.dat 9 | *.out 10 | *.pid 11 | *.swp 12 | *.swo 13 | node_modules 14 | coverage 15 | *.tgz 16 | *.xml 17 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Lines starting with '#' are comments. 2 | # Each line is a file pattern followed by one or more owners, 3 | # the last matching pattern has the most precendence. 4 | 5 | # Core team members from IBM and community contributor 6 | * @jannyHou @fabien @clarkorz 7 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ### Contributing ### 2 | 3 | Thank you for your interest in `loopback-example-model-relations`, an open source project 4 | administered by StrongLoop. 5 | 6 | Contributing to `loopback-example-model-relations` is easy. In a few simple steps: 7 | 8 | * Ensure that your effort is aligned with the project's roadmap by 9 | talking to the maintainers, especially if you are going to spend a 10 | lot of time on it. 11 | 12 | * Make something better or fix a bug. 13 | 14 | * Adhere to code style outlined in the [Google C++ Style Guide][] and 15 | [Google Javascript Style Guide][]. 16 | 17 | * Sign the [Contributor License Agreement](https://cla.strongloop.com/strongloop/loopback-example-model-relations) 18 | 19 | * Submit a pull request through Github. 20 | 21 | 22 | ### Contributor License Agreement ### 23 | 24 | ``` 25 | Individual Contributor License Agreement 26 | 27 | By signing this Individual Contributor License Agreement 28 | ("Agreement"), and making a Contribution (as defined below) to 29 | StrongLoop, Inc. ("StrongLoop"), You (as defined below) accept and 30 | agree to the following terms and conditions for Your present and 31 | future Contributions submitted to StrongLoop. Except for the license 32 | granted in this Agreement to StrongLoop and recipients of software 33 | distributed by StrongLoop, You reserve all right, title, and interest 34 | in and to Your Contributions. 35 | 36 | 1. Definitions 37 | 38 | "You" or "Your" shall mean the copyright owner or the individual 39 | authorized by the copyright owner that is entering into this 40 | Agreement with StrongLoop. 41 | 42 | "Contribution" shall mean any original work of authorship, 43 | including any modifications or additions to an existing work, that 44 | is intentionally submitted by You to StrongLoop for inclusion in, 45 | or documentation of, any of the products owned or managed by 46 | StrongLoop ("Work"). For purposes of this definition, "submitted" 47 | means any form of electronic, verbal, or written communication 48 | sent to StrongLoop or its representatives, including but not 49 | limited to communication or electronic mailing lists, source code 50 | control systems, and issue tracking systems that are managed by, 51 | or on behalf of, StrongLoop for the purpose of discussing and 52 | improving the Work, but excluding communication that is 53 | conspicuously marked or otherwise designated in writing by You as 54 | "Not a Contribution." 55 | 56 | 2. You Grant a Copyright License to StrongLoop 57 | 58 | Subject to the terms and conditions of this Agreement, You hereby 59 | grant to StrongLoop and recipients of software distributed by 60 | StrongLoop, a perpetual, worldwide, non-exclusive, no-charge, 61 | royalty-free, irrevocable copyright license to reproduce, prepare 62 | derivative works of, publicly display, publicly perform, 63 | sublicense, and distribute Your Contributions and such derivative 64 | works under any license and without any restrictions. 65 | 66 | 3. You Grant a Patent License to StrongLoop 67 | 68 | Subject to the terms and conditions of this Agreement, You hereby 69 | grant to StrongLoop and to recipients of software distributed by 70 | StrongLoop a perpetual, worldwide, non-exclusive, no-charge, 71 | royalty-free, irrevocable (except as stated in this Section) 72 | patent license to make, have made, use, offer to sell, sell, 73 | import, and otherwise transfer the Work under any license and 74 | without any restrictions. The patent license You grant to 75 | StrongLoop under this Section applies only to those patent claims 76 | licensable by You that are necessarily infringed by Your 77 | Contributions(s) alone or by combination of Your Contributions(s) 78 | with the Work to which such Contribution(s) was submitted. If any 79 | entity institutes a patent litigation against You or any other 80 | entity (including a cross-claim or counterclaim in a lawsuit) 81 | alleging that Your Contribution, or the Work to which You have 82 | contributed, constitutes direct or contributory patent 83 | infringement, any patent licenses granted to that entity under 84 | this Agreement for that Contribution or Work shall terminate as 85 | of the date such litigation is filed. 86 | 87 | 4. You Have the Right to Grant Licenses to StrongLoop 88 | 89 | You represent that You are legally entitled to grant the licenses 90 | in this Agreement. 91 | 92 | If Your employer(s) has rights to intellectual property that You 93 | create, You represent that You have received permission to make 94 | the Contributions on behalf of that employer, that Your employer 95 | has waived such rights for Your Contributions, or that Your 96 | employer has executed a separate Corporate Contributor License 97 | Agreement with StrongLoop. 98 | 99 | 5. The Contributions Are Your Original Work 100 | 101 | You represent that each of Your Contributions are Your original 102 | works of authorship (see Section 8 (Submissions on Behalf of 103 | Others) for submission on behalf of others). You represent that to 104 | Your knowledge, no other person claims, or has the right to claim, 105 | any right in any intellectual property right related to Your 106 | Contributions. 107 | 108 | You also represent that You are not legally obligated, whether by 109 | entering into an agreement or otherwise, in any way that conflicts 110 | with the terms of this Agreement. 111 | 112 | You represent that Your Contribution submissions include complete 113 | details of any third-party license or other restriction (including, 114 | but not limited to, related patents and trademarks) of which You 115 | are personally aware and which are associated with any part of 116 | Your Contributions. 117 | 118 | 6. You Don't Have an Obligation to Provide Support for Your Contributions 119 | 120 | You are not expected to provide support for Your Contributions, 121 | except to the extent You desire to provide support. You may provide 122 | support for free, for a fee, or not at all. 123 | 124 | 6. No Warranties or Conditions 125 | 126 | StrongLoop acknowledges that unless required by applicable law or 127 | agreed to in writing, You provide Your Contributions on an "AS IS" 128 | BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER 129 | EXPRESS OR IMPLIED, INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES 130 | OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY, OR 131 | FITNESS FOR A PARTICULAR PURPOSE. 132 | 133 | 7. Submission on Behalf of Others 134 | 135 | If You wish to submit work that is not Your original creation, You 136 | may submit it to StrongLoop separately from any Contribution, 137 | identifying the complete details of its source and of any license 138 | or other restriction (including, but not limited to, related 139 | patents, trademarks, and license agreements) of which You are 140 | personally aware, and conspicuously marking the work as 141 | "Submitted on Behalf of a Third-Party: [named here]". 142 | 143 | 8. Agree to Notify of Change of Circumstances 144 | 145 | You agree to notify StrongLoop of any facts or circumstances of 146 | which You become aware that would make these representations 147 | inaccurate in any respect. Email us at callback@strongloop.com. 148 | ``` 149 | 150 | [Google C++ Style Guide]: https://google.github.io/styleguide/cppguide.html 151 | [Google Javascript Style Guide]: https://google.github.io/styleguide/javascriptguide.xml 152 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) IBM Corp. 2015,2017. All Rights Reserved. 2 | Node module: loopback-example-relations 3 | This project is licensed under the MIT License, full text below. 4 | 5 | -------- 6 | 7 | MIT license 8 | 9 | Permission is hereby granted, free of charge, to any person obtaining a copy 10 | of this software and associated documentation files (the "Software"), to deal 11 | in the Software without restriction, including without limitation the rights 12 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | copies of the Software, and to permit persons to whom the Software is 14 | furnished to do so, subject to the following conditions: 15 | 16 | The above copyright notice and this permission notice shall be included in 17 | all copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # loopback-example-relations 2 | 3 | **⚠️ This LoopBack 3 example project is no longer maintained. Please refer to [LoopBack 4 Examples](https://loopback.io/doc/en/lb4/Examples.html) instead. ⚠️** 4 | 5 | ``` 6 | $ git clone https://github.com/strongloop/loopback-example-relations.git 7 | $ cd loopback-example-relations 8 | $ npm install 9 | $ node . 10 | ``` 11 | 12 | In this example, we create a simple web app to demonstrate model relation 13 | concepts. The app consists of a single web page with a list of links to help us 14 | query and filter sample data via REST. 15 | 16 | ## Prerequisites 17 | 18 | Before starting this tutorial, you must install: 19 | - Node.js 20 | - LoopBack CLI tools; see [lb](http://loopback.io/doc/en/lb3/Installation.html) 21 | 22 | ### Tutorials 23 | 24 | - [Getting started with LoopBack](https://github.com/strongloop/loopback-getting-started) 25 | - [Tutorial series - Step 1](https://github.com/strongloop/loopback-getting-started/tree/step1) 26 | 27 | ### Knowledge 28 | 29 | - [LoopBack boot scripts](http://docs.strongloop.com/display/LB/Defining+boot+scripts) 30 | - [LoopBack models](http://docs.strongloop.com/display/LB/Defining+models) 31 | - [Loopback model relations](http://docs.strongloop.com/display/LB/Define+model+relations) 32 | - [Creating loopback model relations](https://docs.strongloop.com/display/public/LB/Creating+model+relations) 33 | 34 | ## Procedure 35 | 36 | ### Create the application 37 | 38 | #### Application information 39 | 40 | - Name: `loopback-example-relations` 41 | - Directory to contain the project: `loopback-example-relations` 42 | 43 | ``` 44 | $ lb app loopback-example-relations 45 | ... # follow the prompts 46 | $ cd loopback-example-relations 47 | ``` 48 | 49 | ### Create the datasource 50 | 51 | - Name: `transient` 52 | - Connector: `other` 53 | - Name: `transient` 54 | 55 | ``` 56 | $ lb datasource 57 | ... # follow the prompts, choose `other` to define custom connector 58 | ``` 59 | 60 | ### Create the models 61 | 62 | #### Model information 63 | 64 | - Name: `Customer` 65 | - Data source: `db (memory)` 66 | - Base class: `PersistedModel` 67 | - Expose over REST: `Yes` 68 | - Custom plural form: *Leave blank* 69 | - Properties: 70 | - `name` 71 | - String 72 | - Not Required 73 | - `age` 74 | - number 75 | - Not Required 76 | - Name: `Order` 77 | - Data source: `db (memory)` 78 | - Base class: `PersistedModel` 79 | - Expose over REST: `Yes` 80 | - Custom plural form: *Leave blank* 81 | - Properties: 82 | - `description` 83 | - String 84 | - Not Required 85 | - `date` 86 | - date 87 | - Not Required 88 | - Name: `Account` 89 | - Data source: `db (memory)` 90 | - Base class: `PersistedModel` 91 | - Expose over REST: `No` 92 | - Custom plural form: *Leave blank* 93 | - Properties: 94 | - `name` 95 | - String 96 | - Not Required 97 | - `date` 98 | - date 99 | - Not Required 100 | - Name: `Address` 101 | - Data source: `transient` 102 | - Base class: `Model` 103 | - Expose over REST: `No` 104 | - Custom plural form: *Leave blank* 105 | - Properties: 106 | - `street` 107 | - String 108 | - Not Required 109 | - `city` 110 | - String 111 | - Not Required 112 | - `state` 113 | - String 114 | - Not Required 115 | - `zipCode` 116 | - String 117 | - Not Required 118 | - Name: `Author` 119 | - Data source: `db (memory)` 120 | - Base class: `PersistedModel` 121 | - Expose over REST: `No` 122 | - Custom plural form: *Leave blank* 123 | - Properties: 124 | - `name` 125 | - String 126 | - Not Required 127 | - Name: `Book` 128 | - Data source: `db (memory)` 129 | - Base class: `PersistedModel` 130 | - Expose over REST: `Yes` 131 | - Custom plural form: *Leave blank* 132 | - Properties: 133 | - `name` 134 | - String 135 | - Not Required 136 | - Name: `EmailAddress` 137 | - Data source: `transient` 138 | - Base class: `PersistedModel` 139 | - Expose over REST: `No` 140 | - Custom plural form: *Leave blank* 141 | - Properties: 142 | - `label` 143 | - String 144 | - Not Required 145 | - `address` 146 | - String 147 | - Not Required 148 | - Name: `Link` 149 | - Data source: `transient` 150 | - Base class: `Model` 151 | - Expose over REST: `No` 152 | - Custom plural form: *Leave blank* 153 | - Properties: 154 | - `id` 155 | - number 156 | - Required 157 | > Please set `"id": true` manually for this property, like [link.json](/common/models/link.json#L8) 158 | - `name` 159 | - String 160 | - Not Required 161 | - `notes` 162 | - String 163 | - Not Required 164 | - Name: `Reader` 165 | - Data source: `db (memory)` 166 | - Base class: `PersistedModel` 167 | - Expose over REST: `No` 168 | - Custom plural form: *Leave blank* 169 | - Properties: 170 | - `name` 171 | - String 172 | - Not Required 173 | - Name: `Shipment` 174 | - Data source: `db (memory)` 175 | - Base class: `PersistedModel` 176 | - Expose over REST: `No` 177 | - Custom plural form: *Leave blank* 178 | - Properties: 179 | - `date` 180 | - Date 181 | - Not Required 182 | - `description` 183 | - String 184 | - Not Required 185 | 186 | ``` 187 | $ lb model Customer 188 | ... # follow the prompts, repeat for other models 189 | ``` 190 | > Upper-case in model's name would be interpreted as '-' in model's file name, eg: EmailAddress has `email-address.json` 191 | 192 | ### Configure server-side views 193 | 194 | > LoopBack comes preconfigured with EJS out-of-box. This means we can use 195 | > server-side templating by simply setting the proper view engine and a 196 | > directory to store the views. 197 | 198 | Create a [`views` directory](https://github.com/strongloop/loopback-example-relations/blob/master/server/views) to store server-side templates. 199 | 200 | ``` 201 | $ mkdir server/views 202 | ``` 203 | 204 | Create [`index.ejs` in the views directory](https://github.com/strongloop/loopback-example-relations/blob/master/server/views/index.ejs). 205 | Create [`account.ejs` in the views directory](https://github.com/strongloop/loopback-example-relations/blob/master/server/views/account.ejs). 206 | Create [`email.ejs` in the views directory](https://github.com/strongloop/loopback-example-relations/blob/master/server/views/email.ejs). 207 | Create [`address.ejs` in the views directory](https://github.com/strongloop/loopback-example-relations/blob/master/server/views/address.ejs). 208 | 209 | [Configure `server.js`](https://github.com/strongloop/loopback-example-relations/blob/master/server/server.js#L5-L11) to use server-side 210 | templating. Remember to import the [`path`](https://github.com/strongloop/loopback-example-relations/blob/master/server/server.js#L3) package. 211 | 212 | ### Replace root 213 | 214 | Replace the default `root.js` with [a new one](https://github.com/strongloop/loopback-example-relations/blob/master/server/boot/root.js) which passes a given customer's id to template engine. 215 | 216 | ### Add sample data 217 | 218 | Create six boot scripts: 219 | 220 | - [`sample-customers.js`](https://github.com/strongloop/loopback-example-relations/blob/master/server/boot/sample-customers.js) 221 | - [`z-book-people.js`](https://github.com/strongloop/loopback-example-relations/blob/master/server/boot/z-book-people.js) 222 | - [`z-customer-accounts.js`](https://github.com/strongloop/loopback-example-relations/blob/master/server/boot/z-customer-accounts.js) 223 | - [`z-customer-address.js`](https://github.com/strongloop/loopback-example-relations/blob/master/server/boot/z-customer-address.js) 224 | - [`z-customer-emails.js`](https://github.com/strongloop/loopback-example-relations/blob/master/server/boot/z-customer-emails.js) 225 | 226 | > We add z- in front of the boot script names to make sure they load last since LoopBack boot loads boot scripts alphabetically. 227 | 228 | ### Create model relations 229 | 230 | #### Model relation information 231 | 232 | - `Customer` 233 | - has many 234 | - `Order` 235 | - Property name for the relation: `orders` 236 | - Custom foreign key: `customerId` 237 | - Require a through model: No 238 | - `Other Relations: `(please add them manually) 239 | 240 | ``` 241 | "address": { 242 | "type": "embedsOne", 243 | "model": "Address", 244 | "property": "billingAddress", 245 | "options": { 246 | "validate": true, 247 | "forceId": false 248 | } 249 | }, 250 | "emails": { 251 | "type": "embedsMany", 252 | "model": "EmailAddress", 253 | "property": "emailList", 254 | "options": { 255 | "validate": true, 256 | "forceId": false 257 | } 258 | }, 259 | "accounts": { 260 | "type": "referencesMany", 261 | "model": "Account", 262 | "property": "accountIds", 263 | "options": { 264 | "validate": true, 265 | "forceId": false 266 | } 267 | }, 268 | ``` 269 | 270 | - `Book`(please add them manually) 271 | 272 | ``` 273 | "people": { 274 | "type": "embedsMany", 275 | "model": "Link", 276 | "scope": { 277 | "include": "linked" 278 | } 279 | } 280 | ``` 281 | 282 | - `Link`(please add them manually) 283 | 284 | ``` 285 | "linked": { 286 | "type": "belongsTo", 287 | "polymorphic": { 288 | "idType": "number" 289 | }, 290 | "properties": { 291 | "name": "name" 292 | }, 293 | "options": { 294 | "invertProperties": true 295 | } 296 | } 297 | ``` 298 | - `Order` 299 | - belongs to 300 | - `Customer` 301 | - Property name for the relation: *Leave blank - defaults to `customer`* 302 | - Custom foreign key: *Leave blank* 303 | - has many 304 | - `Shipment` 305 | - Property name for the relation: *Leave blank - defaults to `shipments`* 306 | - Custom foreign key: *Leave blank* 307 | 308 | - `Shipment` 309 | - belongs to 310 | - `Order` 311 | - Property name for the relation: *Leave blank - defaults to `order`* 312 | - Custom foreign key: *Leave blank* 313 | 314 | ``` 315 | $ lb relation 316 | ? Select the model to create the relationship from: 317 | ... 318 | > Customer 319 | ... # follow the prompts, repeat for other models 320 | ``` 321 | 322 | > Some relations are not available in `lb`, please add them in `model-name.json` manually. 323 | > LoopBack [automatically derives](http://docs.strongloop.com/display/LB/BelongsTo+relations#BelongsTorelations-Overview) 324 | > relation and foreign key names when you leave the values empty. 325 | 326 | ### Try the API 327 | 328 | Start the application with `node .` and browse to [`localhost:3000`][localhost]. 329 | You should see various links. Each endpoint is defined as follows: 330 | 331 | #### hasMany 332 | - [/api/customers](http://localhost:3000/api/customers) 333 | - List all customers 334 | - [/api/customers/4](http://localhost:3000/api/customers/4) 335 | - Look up a customer by ID 336 | - [/api/customers/youngFolks](http://localhost:3000/api/customers/youngFolks) 337 | - List a predefined scope named *youngFolks* 338 | - [/api/customers/4/orders](http://localhost:3000/api/customers/4/orders) 339 | - List a given customer's orders 340 | - [/api/customers?filter[include]=orders](http://localhost:3000/api/customers?filter[include]=orders) 341 | - List all customers including their orders 342 | - [/api/customers?filter[include][orders]=customer](http://localhost:3000/api/customers?filter[include][orders]=customer) 343 | - List all customers including their orders which also include the customer 344 | - [/api/customers?filter[include][orders]=customer&filter[where][age]=21](http://localhost:3000/api/customers?filter[include][orders]=customer&filter[where][age]=21) 345 | - List all customers whose age is 21, including their orders 346 | - [/api/customers?filter[include][orders]=customer&filter[limit]=2](http://localhost:3000/api/customers?filter[include][orders]=customer&filter[limit]=2) 347 | - List first two customers including their orders 348 | - [/api/customers?filter[include]=accounts&filter[include]=orders](http://localhost:3000/api/customers?filter[include]=accounts&filter[include]=orders) 349 | - List all customers including their accounts and orders 350 | 351 | #### embedsOne 352 | - [/api/customers/2/address](http://localhost:3000/api/customers/2/address) 353 | - List a given customer's address 354 | 355 | #### embedsMany 356 | - [/api/customers/3/emails](http://localhost:3000/api/customers/3/emails) 357 | - List a given customer's email 358 | 359 | #### referencesMany 360 | - [api/customers/1/accounts](http://localhost:3000/api/customers/1/accounts) 361 | - List all accounts owned by a given customer 362 | - [/api/customers/1/accounts/1](http://localhost:3000/api/customers/1/accounts/1) 363 | - Look up a given accounts of a given customer by foreign key 364 | - [/api/customers/1/accounts/count](http://localhost:3000/api/customers/1/accounts/count) 365 | - Count accounts number of a given customer 366 | 367 | #### polymorphic embedsMany 368 | - [/api/Books/1/people](http://localhost:3000/api/Books/1/people) 369 | - List the linked people of a given book 370 | - [/api/Books/1/people/1](http://localhost:3000/api/Books/1/people/1) 371 | - Look up the author of a given book by foreign key 372 | - [/api/Books/1/people/2](http://localhost:3000/api/Books/1/people/2) 373 | - Look up the reader of a given book by foreign key 374 | 375 | --- 376 | 377 | [More LoopBack examples](https://loopback.io/doc/en/lb3/Tutorials-and-examples.html) 378 | -------------------------------------------------------------------------------- /client/README.md: -------------------------------------------------------------------------------- 1 | ## Client 2 | 3 | This is the place for your application front-end files. 4 | -------------------------------------------------------------------------------- /common/models/account.js: -------------------------------------------------------------------------------- 1 | // Copyright IBM Corp. 2015,2017. All Rights Reserved. 2 | // Node module: loopback-example-relations 3 | // This file is licensed under the MIT License. 4 | // License text available at https://opensource.org/licenses/MIT 5 | 6 | module.exports = function(Account) { 7 | 8 | }; 9 | -------------------------------------------------------------------------------- /common/models/account.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Account", 3 | "base": "PersistedModel", 4 | "idInjection": true, 5 | "properties": { 6 | "name": { 7 | "type": "string" 8 | }, 9 | "balance": { 10 | "type": "number" 11 | } 12 | }, 13 | "validations": [], 14 | "relations": {}, 15 | "acls": [], 16 | "methods": {} 17 | } 18 | -------------------------------------------------------------------------------- /common/models/address.js: -------------------------------------------------------------------------------- 1 | // Copyright IBM Corp. 2015,2017. All Rights Reserved. 2 | // Node module: loopback-example-relations 3 | // This file is licensed under the MIT License. 4 | // License text available at https://opensource.org/licenses/MIT 5 | 6 | module.exports = function(Address) { 7 | 8 | }; -------------------------------------------------------------------------------- /common/models/address.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Address", 3 | "base": "Model", 4 | "idInjection": true, 5 | "properties": { 6 | "street": { 7 | "type": "string" 8 | }, 9 | "city": { 10 | "type": "string" 11 | }, 12 | "state": { 13 | "type": "string" 14 | }, 15 | "zipCode": { 16 | "type": "string" 17 | } 18 | }, 19 | "validations": [], 20 | "relations": {}, 21 | "acls": [], 22 | "methods": {} 23 | } 24 | -------------------------------------------------------------------------------- /common/models/author.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Author", 3 | "base": "PersistedModel", 4 | "idInjection": true, 5 | "properties": { 6 | "name": { 7 | "type": "string" 8 | } 9 | }, 10 | "validations": [], 11 | "relations": {}, 12 | "acls": [], 13 | "methods": {} 14 | } 15 | -------------------------------------------------------------------------------- /common/models/book.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Book", 3 | "base": "PersistedModel", 4 | "idInjection": true, 5 | "properties": { 6 | "name": { 7 | "type": "string" 8 | } 9 | }, 10 | "validations": [], 11 | "relations": { 12 | "people": { 13 | "type": "embedsMany", 14 | "model": "Link", 15 | "scope": { 16 | "include": "linked" 17 | } 18 | } 19 | }, 20 | "acls": [], 21 | "methods": {} 22 | } 23 | -------------------------------------------------------------------------------- /common/models/customer.js: -------------------------------------------------------------------------------- 1 | // Copyright IBM Corp. 2015,2017. All Rights Reserved. 2 | // Node module: loopback-example-relations 3 | // This file is licensed under the MIT License. 4 | // License text available at https://opensource.org/licenses/MIT 5 | 6 | module.exports = function(Customer) { 7 | 8 | }; 9 | -------------------------------------------------------------------------------- /common/models/customer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Customer", 3 | "base": "PersistedModel", 4 | "idInjection": true, 5 | "properties": { 6 | "name": { 7 | "type": "string" 8 | }, 9 | "age": { 10 | "type": "number" 11 | } 12 | }, 13 | "validations": [], 14 | "relations": { 15 | "address": { 16 | "type": "embedsOne", 17 | "model": "Address", 18 | "property": "billingAddress", 19 | "options": { 20 | "validate": true, 21 | "forceId": false 22 | } 23 | }, 24 | "emails": { 25 | "type": "embedsMany", 26 | "model": "EmailAddress", 27 | "property": "emailList", 28 | "options": { 29 | "validate": true, 30 | "forceId": false 31 | } 32 | }, 33 | "accounts": { 34 | "type": "referencesMany", 35 | "model": "Account", 36 | "foreignKey": "accountIds", 37 | "options": { 38 | "validate": true, 39 | "forceId": false 40 | } 41 | }, 42 | "orders": { 43 | "type": "hasMany", 44 | "model": "Order", 45 | "foreignKey": "" 46 | } 47 | }, 48 | "acls": [], 49 | "methods": {} 50 | } 51 | -------------------------------------------------------------------------------- /common/models/email-address.js: -------------------------------------------------------------------------------- 1 | // Copyright IBM Corp. 2015,2017. All Rights Reserved. 2 | // Node module: loopback-example-relations 3 | // This file is licensed under the MIT License. 4 | // License text available at https://opensource.org/licenses/MIT 5 | 6 | module.exports = function(EmailAddress) { 7 | 8 | }; 9 | -------------------------------------------------------------------------------- /common/models/email-address.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "EmailAddress", 3 | "base": "Model", 4 | "idInjection": true, 5 | "properties": { 6 | "label": { 7 | "type": "string" 8 | }, 9 | "address": { 10 | "type": "string" 11 | } 12 | }, 13 | "validations": [], 14 | "relations": {}, 15 | "acls": [], 16 | "methods": {} 17 | } 18 | -------------------------------------------------------------------------------- /common/models/link.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Link", 3 | "base": "Model", 4 | "idInjection": true, 5 | "properties": { 6 | "id": { 7 | "type": "number", 8 | "id": true 9 | }, 10 | "name": { 11 | "type": "string" 12 | }, 13 | "notes": { 14 | "type": "string" 15 | } 16 | }, 17 | "validations": [], 18 | "relations": { 19 | "linked": { 20 | "type": "belongsTo", 21 | "polymorphic": { 22 | "idType": "number" 23 | }, 24 | "properties": { 25 | "name": "name" 26 | }, 27 | "options": { 28 | "invertProperties": true 29 | } 30 | } 31 | }, 32 | "acls": [], 33 | "methods": {} 34 | } 35 | -------------------------------------------------------------------------------- /common/models/order.js: -------------------------------------------------------------------------------- 1 | // Copyright IBM Corp. 2015,2017. All Rights Reserved. 2 | // Node module: loopback-example-relations 3 | // This file is licensed under the MIT License. 4 | // License text available at https://opensource.org/licenses/MIT 5 | 6 | module.exports = function(Order) { 7 | 8 | }; 9 | -------------------------------------------------------------------------------- /common/models/order.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Order", 3 | "base": "PersistedModel", 4 | "idInjection": true, 5 | "options": { 6 | "validateUpsert": true 7 | }, 8 | "properties": { 9 | "date": { 10 | "type": "date" 11 | }, 12 | "description": { 13 | "type": "string" 14 | } 15 | }, 16 | "validations": [], 17 | "relations": { 18 | "customer": { 19 | "type": "belongsTo", 20 | "model": "Customer", 21 | "foreignKey": "" 22 | }, 23 | "shipments": { 24 | "type": "hasMany", 25 | "model": "Shipment", 26 | "foreignKey": "" 27 | } 28 | }, 29 | "acls": [], 30 | "methods": {} 31 | } 32 | -------------------------------------------------------------------------------- /common/models/reader.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Reader", 3 | "base": "PersistedModel", 4 | "idInjection": true, 5 | "properties": { 6 | "name": { 7 | "type": "string" 8 | } 9 | }, 10 | "validations": [], 11 | "relations": {}, 12 | "acls": [], 13 | "methods": {} 14 | } 15 | -------------------------------------------------------------------------------- /common/models/shipment.js: -------------------------------------------------------------------------------- 1 | // Copyright IBM Corp. 2017. All Rights Reserved. 2 | // Node module: loopback-example-relations 3 | // This file is licensed under the MIT License. 4 | // License text available at https://opensource.org/licenses/MIT 5 | 6 | module.exports = function(Order) { 7 | 8 | }; 9 | -------------------------------------------------------------------------------- /common/models/shipment.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Shipment", 3 | "base": "PersistedModel", 4 | "idInjection": true, 5 | "options": { 6 | "validateUpsert": true 7 | }, 8 | "properties": { 9 | "date": { 10 | "type": "date" 11 | }, 12 | "description": { 13 | "type": "string" 14 | } 15 | }, 16 | "validations": [], 17 | "relations": { 18 | "order": { 19 | "type": "belongsTo", 20 | "model": "Order", 21 | "foreignKey": "" 22 | } 23 | }, 24 | "acls": [], 25 | "methods": {} 26 | } 27 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "loopback-example-relations", 3 | "version": "1.0.0", 4 | "description": "LoopBack model relations example", 5 | "engines": { 6 | "node": ">=4" 7 | }, 8 | "main": "server/server.js", 9 | "scripts": { 10 | "pretest": "jshint .", 11 | "start": "node .", 12 | "test": "mocha -R spec test" 13 | }, 14 | "dependencies": { 15 | "async": "^2.5.0", 16 | "compression": "^1.7.0", 17 | "cors": "^2.8.1", 18 | "ejs": "^2.0.8", 19 | "loopback": "^3.0.0", 20 | "loopback-boot": "^2.12.1", 21 | "loopback-component-explorer": "^5.1.0", 22 | "loopback-connector-rest": "^3.0.0", 23 | "serve-favicon": "^2.4.4", 24 | "serve-static": "^1.10.0", 25 | "strong-error-handler": "^2.2.0" 26 | }, 27 | "devDependencies": { 28 | "jshint": "^2.5.6", 29 | "mocha": "^3.5.3", 30 | "supertest": "^3.0.0" 31 | }, 32 | "repository": { 33 | "type": "git", 34 | "url": "git@github.com:strongloop/loopback-example-relations.git" 35 | }, 36 | "license": "MIT", 37 | "author": "IBM Corp." 38 | } 39 | -------------------------------------------------------------------------------- /server/boot/a-customer-nest-remoting.js: -------------------------------------------------------------------------------- 1 | // Copyright IBM Corp. 2017. All Rights Reserved. 2 | // Node module: loopback-example-relations 3 | // This file is licensed under the MIT License. 4 | // License text available at https://opensource.org/licenses/MIT 5 | 6 | module.exports = function(app) { 7 | var Customer = app.models.Customer; 8 | Customer.nestRemoting('orders'); 9 | }; -------------------------------------------------------------------------------- /server/boot/authentication.js: -------------------------------------------------------------------------------- 1 | // Copyright IBM Corp. 2015,2017. All Rights Reserved. 2 | // Node module: loopback-example-relations 3 | // This file is licensed under the MIT License. 4 | // License text available at https://opensource.org/licenses/MIT 5 | 6 | module.exports = function enableAuthentication(server) { 7 | // enable authentication 8 | server.enableAuth(); 9 | }; 10 | -------------------------------------------------------------------------------- /server/boot/root.js: -------------------------------------------------------------------------------- 1 | // Copyright IBM Corp. 2015,2017. All Rights Reserved. 2 | // Node module: loopback-example-relations 3 | // This file is licensed under the MIT License. 4 | // License text available at https://opensource.org/licenses/MIT 5 | 6 | module.exports = function(app) { 7 | var router = app.loopback.Router(); 8 | router.get('/', function(req, res, next) { 9 | app.models.Customer.findOne({ 10 | where: { 11 | name: 'Customer A' 12 | }}, function(err, customer) { 13 | if (err) return next(err); 14 | res.render('index', {customer: customer}); 15 | }); 16 | }); 17 | router.get('/email', function(req, res, next) { 18 | app.models.Customer.findOne({ 19 | where: { 20 | name: 'Larry Smith' 21 | }}, function(err, customer) { 22 | if (err) return next(err); 23 | res.render('email', {customer: customer}); 24 | }); 25 | }); 26 | router.get('/address', function(req, res, next) { 27 | app.models.Customer.findOne({ 28 | where: { 29 | name: 'John Smith' 30 | }}, function(err, customer) { 31 | if (err) return next(err); 32 | res.render('address', {customer: customer}); 33 | }); 34 | }); 35 | router.get('/account', function(req, res, next) { 36 | app.models.Customer.findOne({ 37 | where: { 38 | name: 'Mary Smith' 39 | }}, function(err, customer) { 40 | if (err) return next(err); 41 | res.render('account', {customer: customer}); 42 | }); 43 | }); 44 | app.use(router); 45 | }; 46 | -------------------------------------------------------------------------------- /server/boot/sample-customers.js: -------------------------------------------------------------------------------- 1 | // Copyright IBM Corp. 2015,2017. All Rights Reserved. 2 | // Node module: loopback-example-relations 3 | // This file is licensed under the MIT License. 4 | // License text available at https://opensource.org/licenses/MIT 5 | 6 | module.exports = function(app) { 7 | var Customer = app.models.Customer; 8 | var Order = app.models.Order; 9 | 10 | // define a custom scope 11 | Customer.scope('youngFolks', {where: {age: {lte: 22 }}}); 12 | app.dataSources.db.automigrate('Customer', function(err) { 13 | if (err) throw err; 14 | 15 | var customers = [ 16 | {name: 'Customer A', age: 21}, 17 | {name: 'Customer B', age: 22}, 18 | {name: 'Customer C', age: 23}, 19 | {name: 'Customer D', age: 24}, 20 | {age: 25} 21 | ]; 22 | var orders = [ 23 | { 24 | description: 'First order by Customer A', 25 | date: '01-01-2015' 26 | }, 27 | { 28 | description: 'Second order by Customer A', 29 | date: '02-01-2015' 30 | }, 31 | { 32 | description: 'Order by Customer B', 33 | date: '03-01-2015' 34 | }, 35 | { 36 | description: 'Order by Customer C', 37 | date: '04-01-2015' 38 | }, 39 | { 40 | description: 'Order by Anonymous', 41 | date: '05-01-2015' 42 | } 43 | ]; 44 | 45 | // Create customers and orders 46 | Customer.create(customers[0], function(err, instance) { 47 | if (err) return console.error(err); 48 | console.log('Customer created: ', instance); 49 | orders[0].customerId = instance.id; 50 | orders[1].customerId = instance.id; 51 | Order.create(orders[0], function(err, instance) { 52 | if (err) return console.error(err); 53 | console.log('Order created: ', instance); 54 | }); 55 | Order.create(orders[1], function(err, instance) { 56 | if (err) return console.error(err); 57 | console.log('Order created: ', instance); 58 | }); 59 | }); 60 | Customer.create(customers[1], function(err, instance) { 61 | if (err) return console.error(err); 62 | console.log('Customer created: ', instance); 63 | orders[2].customerId = instance.id; 64 | Order.create(orders[2], function(err, instance) { 65 | if (err) return console.error(err); 66 | console.log('Order created: ', instance); 67 | }); 68 | }); 69 | Customer.create(customers[2], function(err, instance) { 70 | if (err) return console.error(err); 71 | console.log('Customer created: ', instance); 72 | orders[3].customerId = instance.id; 73 | Order.create(orders[3], function(err, instance) { 74 | if (err) return console.error(err); 75 | console.log('Order created: ', instance); 76 | }); 77 | }); 78 | Customer.create(customers[3], function(err, instance) { 79 | if (err) return console.error(err); 80 | console.log('Customer created: ', instance); 81 | instance.orders.create(orders[4], function(err, instance) { 82 | if (err) return console.error(err); 83 | console.log('Order created: ', instance); 84 | instance.shipments.create({date: new Date(), description: 'Shipment'}, 85 | function(err, instance) { 86 | if (err) return console.error(err); 87 | console.log('Shipment created: ', instance); 88 | }); 89 | }); 90 | }); 91 | }); 92 | }; 93 | -------------------------------------------------------------------------------- /server/boot/z-book-people.js: -------------------------------------------------------------------------------- 1 | // Copyright IBM Corp. 2015,2017. All Rights Reserved. 2 | // Node module: loopback-example-relations 3 | // This file is licensed under the MIT License. 4 | // License text available at https://opensource.org/licenses/MIT 5 | 6 | var async = require('async'); 7 | 8 | module.exports = function(app, cb) { 9 | var Book = app.models.Book; 10 | var Author = app.models.Author; 11 | var Reader = app.models.Reader; 12 | 13 | var book; 14 | var author; 15 | var reader; 16 | async.series([ 17 | function createAuthor(done) { 18 | Author.create({name: 'Author 1'}, function(err, people) { 19 | author = people; 20 | done(err, people); 21 | }); 22 | }, 23 | function createReader(done) { 24 | Reader.create({name: 'Reader 1'}, function(err, people) { 25 | reader = people; 26 | done(err, people); 27 | }); 28 | }, 29 | function createBook(done) { 30 | Book.create({name: 'Book 1'}, function(err, book) { 31 | var link1 = book.people.build({notes: 'Note 1'}); 32 | link1.linked(author); 33 | var link2 = book.people.build({notes: 'Note 2'}); 34 | link2.linked(reader); 35 | console.log('Book:', book); 36 | book.save(done); 37 | }); 38 | } 39 | ], function(err) { 40 | console.log('done'); 41 | cb(err); 42 | }); 43 | }; 44 | -------------------------------------------------------------------------------- /server/boot/z-customer-accounts.js: -------------------------------------------------------------------------------- 1 | // Copyright IBM Corp. 2015,2017. All Rights Reserved. 2 | // Node module: loopback-example-relations 3 | // This file is licensed under the MIT License. 4 | // License text available at https://opensource.org/licenses/MIT 5 | 6 | var async = require('async'); 7 | 8 | module.exports = function(app, cb) { 9 | var Customer = app.models.Customer; 10 | var accounts = [ 11 | { 12 | name: 'Checking', 13 | balance: 5000 14 | }, 15 | { 16 | name: 'Saving', 17 | balance: 2000 18 | } 19 | ]; 20 | Customer.create({name: 'Mary Smith'}, function(err, customer) { 21 | console.log('Customer:', customer); 22 | async.each(accounts, function(account, done) { 23 | customer.accounts.create(account, done); 24 | }, function(err) { 25 | console.log('Customer with accounts:', customer); 26 | customer.accounts(console.log); 27 | cb(err); 28 | }); 29 | }); 30 | }; 31 | -------------------------------------------------------------------------------- /server/boot/z-customer-address.js: -------------------------------------------------------------------------------- 1 | // Copyright IBM Corp. 2015,2017. All Rights Reserved. 2 | // Node module: loopback-example-relations 3 | // This file is licensed under the MIT License. 4 | // License text available at https://opensource.org/licenses/MIT 5 | 6 | module.exports = function(app) { 7 | var Customer = app.models.Customer; 8 | var address = { 9 | street: '123 A St', 10 | city: 'San Jose', 11 | state: 'CA', 12 | zipCode: '95131' 13 | }; 14 | Customer.create({name: 'John Smith'}, function(err, customer) { 15 | console.log('Customer:', customer); 16 | customer.address.create(address, function(err, address) { 17 | console.log('Address:', address); 18 | console.log('Customer with address:', customer); 19 | }); 20 | }); 21 | }; 22 | -------------------------------------------------------------------------------- /server/boot/z-customer-emails.js: -------------------------------------------------------------------------------- 1 | // Copyright IBM Corp. 2015,2017. All Rights Reserved. 2 | // Node module: loopback-example-relations 3 | // This file is licensed under the MIT License. 4 | // License text available at https://opensource.org/licenses/MIT 5 | 6 | var async = require('async'); 7 | 8 | module.exports = function(app, cb) { 9 | var Customer = app.models.Customer; 10 | var emails = [ 11 | { 12 | label: 'work', 13 | address: 'larry@xyz.com' 14 | }, 15 | { 16 | name: 'home', 17 | address: 'larry@gmail.com' 18 | } 19 | ]; 20 | Customer.create({name: 'Larry Smith'}, function(err, customer) { 21 | console.log('Customer:', customer); 22 | async.each(emails, function(email, done) { 23 | customer.emails.create(email, done); 24 | }, function(err) { 25 | console.log('Customer with emails:', customer); 26 | var id1 = customer.emailList[0].id; 27 | var id2 = customer.emailList[1].id; 28 | async.series([ 29 | // Find an email by id 30 | function(done) { 31 | customer.emails.get(id1, function(err, email) { 32 | console.log('Email:', email); 33 | done(); 34 | }); 35 | }, 36 | function(done) { 37 | customer.emails.set(id2, {label: 'home', address: 'larry@yahoo.com'}, 38 | function(err, email) { 39 | done(); 40 | }); 41 | }, 42 | // Remove an email by id 43 | function(done) { 44 | customer.emails.unset(id1, function(err) { 45 | done(); 46 | }); 47 | } 48 | ], function(err) { 49 | console.log('Customer with emails:', customer); 50 | cb(err); 51 | }); 52 | }); 53 | }); 54 | }; 55 | -------------------------------------------------------------------------------- /server/component-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "loopback-component-explorer": { 3 | "mountPath": "/explorer" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /server/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "restApiRoot": "/api", 3 | "host": "0.0.0.0", 4 | "port": 3000, 5 | "remoting": { 6 | "context": false, 7 | "rest": { 8 | "normalizeHttpPath": false, 9 | "xml": false 10 | }, 11 | "json": { 12 | "strict": false, 13 | "limit": "100kb" 14 | }, 15 | "urlencoded": { 16 | "extended": true, 17 | "limit": "100kb" 18 | }, 19 | "cors": false, 20 | "handleErrors": false 21 | }, 22 | "legacyExplorer": false 23 | } 24 | -------------------------------------------------------------------------------- /server/datasources.json: -------------------------------------------------------------------------------- 1 | { 2 | "db": { 3 | "name": "db", 4 | "connector": "memory" 5 | }, 6 | "transient": { 7 | "name": "transient", 8 | "connector": "transient" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /server/middleware.json: -------------------------------------------------------------------------------- 1 | { 2 | "initial:before": { 3 | "loopback#favicon": {} 4 | }, 5 | "initial": { 6 | "compression": {}, 7 | "cors": { 8 | "params": { 9 | "origin": true, 10 | "credentials": true, 11 | "maxAge": 86400 12 | } 13 | } 14 | }, 15 | "session": { 16 | }, 17 | "auth": { 18 | }, 19 | "parse": { 20 | }, 21 | "routes:before": { 22 | "loopback#rest": { 23 | "paths": ["${restApiRoot}"] 24 | } 25 | }, 26 | "files": { 27 | "serve-static": { 28 | "params": "$!../client" 29 | } 30 | }, 31 | "final": { 32 | "loopback#urlNotFound": {} 33 | }, 34 | "final:after": { 35 | "strong-error-handler": {} 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /server/model-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "_meta": { 3 | "sources": [ 4 | "loopback/common/models", 5 | "loopback/server/models", 6 | "../common/models", 7 | "./models" 8 | ] 9 | }, 10 | "User": { 11 | "dataSource": "db" 12 | }, 13 | "AccessToken": { 14 | "dataSource": "db", 15 | "public": false 16 | }, 17 | "ACL": { 18 | "dataSource": "db", 19 | "public": false 20 | }, 21 | "RoleMapping": { 22 | "dataSource": "db", 23 | "public": false 24 | }, 25 | "Role": { 26 | "dataSource": "db", 27 | "public": false 28 | }, 29 | "Customer": { 30 | "dataSource": "db", 31 | "public": true 32 | }, 33 | "Address": { 34 | "dataSource": "transient", 35 | "public": false 36 | }, 37 | "EmailAddress": { 38 | "dataSource": "transient", 39 | "public": false 40 | }, 41 | "Account": { 42 | "dataSource": "db", 43 | "public": false 44 | }, 45 | "Author": { 46 | "dataSource": "db", 47 | "public": false 48 | }, 49 | "Reader": { 50 | "dataSource": "db", 51 | "public": false 52 | }, 53 | "Book": { 54 | "dataSource": "db", 55 | "public": true 56 | }, 57 | "Link": { 58 | "dataSource": "transient", 59 | "public": false 60 | }, 61 | "Order": { 62 | "dataSource": "db", 63 | "public": true 64 | }, 65 | "Shipment": { 66 | "dataSource": "db", 67 | "public": true 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /server/server.js: -------------------------------------------------------------------------------- 1 | // Copyright IBM Corp. 2015,2017. All Rights Reserved. 2 | // Node module: loopback-example-relations 3 | // This file is licensed under the MIT License. 4 | // License text available at https://opensource.org/licenses/MIT 5 | 6 | var loopback = require('loopback'); 7 | var boot = require('loopback-boot'); 8 | var path = require('path'); 9 | 10 | var app = module.exports = loopback(); 11 | app.set('view engine', 'ejs'); // LoopBack comes with EJS out-of-box 12 | app.set('json spaces', 2); // format json responses for easier viewing 13 | 14 | // must be set to serve views properly when starting the app via `slc run` from 15 | // the project root 16 | app.set('views', path.resolve(__dirname, 'views')); 17 | 18 | app.start = function() { 19 | // start the web server 20 | return app.listen(function() { 21 | app.emit('started'); 22 | var baseUrl = app.get('url').replace(/\/$/, ''); 23 | console.log('Web server listening at: %s', baseUrl); 24 | if (app.get('loopback-component-explorer')) { 25 | var explorerPath = app.get('loopback-component-explorer').mountPath; 26 | console.log('Browse your REST API at %s%s', baseUrl, explorerPath); 27 | } 28 | }); 29 | }; 30 | 31 | // Bootstrap the application, configure models, datasources and middleware. 32 | // Sub-apps like REST API are mounted via boot scripts. 33 | boot(app, __dirname, function(err) { 34 | if (err) throw err; 35 | 36 | // start the server if `$ node server.js` 37 | if (require.main === module) 38 | app.start(); 39 | }); 40 | -------------------------------------------------------------------------------- /server/views/account.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | loopback-example-model-relations 5 | 6 | 7 |

loopback-example-model-relations

8 |

9 | API Explorer 10 |

11 |

API

12 |

referencesMany

13 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /server/views/address.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | loopback-example-model-relations 5 | 6 | 7 |

loopback-example-model-relations

8 |

9 | API Explorer 10 |

11 |

API

12 |

embedsOne

13 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /server/views/email.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | loopback-example-model-relations 5 | 6 | 7 |

loopback-example-model-relations

8 |

9 | API Explorer 10 |

11 |

API

12 |

embedsMany

13 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /server/views/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | loopback-example-model-relations 5 | 6 | 7 |

loopback-example-model-relations

8 |

9 | API Explorer 10 |

11 |

API

12 |

hasMany

13 | 25 |

polymorphic embedsMany

26 | 31 |

embedsOne

32 | Click to see Endpoints 33 |

embedsMany

34 | Click to see Endpoints 35 |

referencesMany

36 | Click to see Endpoints 37 | 38 | 39 | -------------------------------------------------------------------------------- /test/rest_api_test.js: -------------------------------------------------------------------------------- 1 | // Copyright IBM Corp. 2015,2017. All Rights Reserved. 2 | // Node module: loopback-example-relations 3 | // This file is licensed under the MIT License. 4 | // License text available at https://opensource.org/licenses/MIT 5 | 6 | /* jshint camelcase: false */ 7 | 'use strict'; 8 | var app = require('../server/server'); 9 | var request = require('supertest'); 10 | var assert = require('assert'); 11 | var loopback = require('loopback'); 12 | 13 | function json(verb, url) { 14 | return request(app)[verb](url) 15 | .set('Content-Type', 'application/json') 16 | .set('Accept', 'application/json') 17 | .expect('Content-Type', /json/); 18 | } 19 | 20 | describe('REST API request', function() { 21 | before(function(done) { 22 | require('./start-server'); 23 | done(); 24 | }); 25 | 26 | after(function(done) { 27 | app.removeAllListeners('started'); 28 | app.removeAllListeners('loaded'); 29 | done(); 30 | }); 31 | 32 | it('should return a list of all customers', function(done) { 33 | json('get', '/api/customers') 34 | .expect(200, function(err, res){ 35 | assert(Array.isArray(res.body)); 36 | assert.equal(res.body.length, 7); 37 | done(); 38 | }); 39 | }); 40 | 41 | it('should return a list of all customers with names', function(done){ 42 | json('get', '/api/customers?filter[fields][name]=true') 43 | .expect(200, function(err, res){ 44 | assert(Array.isArray(res.body)); 45 | assert.equal(res.body.length, 7); 46 | done(); 47 | }); 48 | }); 49 | 50 | it('should return a customer with id 1', function(done) { 51 | json('get', '/api/customers/1') 52 | .expect(200, function(err, res){ 53 | assert(res.body.name); 54 | assert(res.body.age); 55 | assert(res.body.id); 56 | assert.equal(res.body.id, 1); 57 | done(); 58 | }); 59 | }); 60 | 61 | it('should return a customer with age less than 22', function(done) { 62 | json('get', '/api/customers/youngFolks') 63 | .expect(200, function(err, res){ 64 | assert(Array.isArray(res.body)); 65 | assert.notEqual(res.body.length, 0); 66 | assert(res.body[0].name); 67 | assert(res.body[0].age); 68 | assert(res.body[0].id); 69 | done(); 70 | }); 71 | }); 72 | 73 | it('should return orders by customer 1', function(done) { 74 | json('get', '/api/customers/1/orders') 75 | .expect(200, function(err, res){ 76 | assert(Array.isArray(res.body)); 77 | assert.notEqual(res.body.length, 0); 78 | assert(res.body[0].description); 79 | assert(res.body[0].date); 80 | assert(res.body[0].id); 81 | assert.equal(res.body[0].customerId, 1); 82 | done(); 83 | }); 84 | }); 85 | 86 | it('should allow nesting relations', function(done) { 87 | json('get', '/api/customers/4/orders/5/shipments') 88 | .expect(200, function(err, res){ 89 | assert(Array.isArray(res.body)); 90 | assert.notEqual(res.body.length, 0); 91 | assert(res.body[0].description); 92 | assert(res.body[0].date); 93 | assert(res.body[0].id); 94 | assert.equal(res.body[0].orderId, 5); 95 | done(); 96 | }); 97 | }); 98 | 99 | it('should return only 2 customers', function(done) { 100 | json('get', 101 | '/api/customers?filter[include][orders]=customer&filter[limit]=2') 102 | .expect(200, function(err,res){ 103 | assert(Array.isArray(res.body)); 104 | assert.equal(res.body.length, 2); 105 | done(); 106 | }); 107 | }); 108 | 109 | it('should return customer address', function(done) { 110 | json('get', '/api/customers/6/address') 111 | .expect(200, function(err,res){ 112 | assert(res.body.street); 113 | assert(res.body.city); 114 | assert(res.body.state); 115 | assert(res.body.zipCode); 116 | assert(res.body.id); 117 | done(); 118 | }); 119 | }); 120 | 121 | it('should return customer emails', function(done) { 122 | json('get', '/api/customers/7/emails') 123 | .expect(200, function(err,res){ 124 | assert(Array.isArray(res.body)); 125 | assert(res.body[0].label); 126 | assert(res.body[0].address); 127 | assert(res.body[0].id); 128 | assert(res.body[0].name); 129 | done(); 130 | }); 131 | }); 132 | 133 | it('should return customer accounts', function(done) { 134 | json('get', '/api/customers/5/accounts') 135 | .expect(200, function(err,res){ 136 | assert(Array.isArray(res.body)); 137 | assert(res.body[0].name); 138 | assert(res.body[0].balance); 139 | assert(res.body[0].id); 140 | done(); 141 | }); 142 | }); 143 | 144 | it('should return accounts number', function(done) { 145 | json('get', '/api/customers/5/accounts/count') 146 | .expect(200, function(err,res){ 147 | assert.equal(res.body.count, 2); 148 | done(); 149 | }); 150 | }); 151 | 152 | it('should return people linked to book', function(done) { 153 | json('get', '/api/books/1/people') 154 | .expect(200, function(err,res){ 155 | assert(Array.isArray(res.body)); 156 | assert(res.body[0].id); 157 | assert(res.body[0].name); 158 | assert(res.body[0].notes); 159 | assert(res.body[0].linkedId); 160 | assert(res.body[0].linkedType); 161 | done(); 162 | }); 163 | }); 164 | 165 | it('should return book author', function(done) { 166 | json('get', '/api/books/1/people/1') 167 | .expect(200, function(err,res){ 168 | assert.equal(res.body.linkedType, 'Author'); 169 | done(); 170 | }); 171 | }); 172 | }); 173 | 174 | 175 | describe('Unexpected Usage', function(){ 176 | it('should not crash the server when posting a bad id', function(done){ 177 | json('post', '/api/customers/foobar') 178 | .send({}) 179 | .expect(404, done); 180 | }); 181 | }); 182 | -------------------------------------------------------------------------------- /test/start-server.js: -------------------------------------------------------------------------------- 1 | // Copyright IBM Corp. 2015,2017. All Rights Reserved. 2 | // Node module: loopback-example-relations 3 | // This file is licensed under the MIT License. 4 | // License text available at https://opensource.org/licenses/MIT 5 | 6 | var app = require('../server/server'); 7 | 8 | module.exports = function(done) { 9 | if (app.loaded) { 10 | app.once('started', done); 11 | app.start(); 12 | } else { 13 | app.once('loaded', function() { 14 | app.once('started', done); 15 | app.start(); 16 | }); 17 | } 18 | }; 19 | --------------------------------------------------------------------------------