Repository navigation
Expand file tree
/
Copy pathfeed.xml
More file actions
1354 lines (1108 loc) · 72 KB
/
Copy pathfeed.xml
File metadata and controls
1354 lines (1108 loc) · 72 KB
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
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0">
<channel>
<title>codeaddslife</title>
<link>https://www.codeaddslife.com</link>
<item>
<title>Loopback</title>
<link>https://www.codeaddslife.com/articles/loopback.html</link>
<description><h1 id="loopback">Loopback</h1>
<p><a href="http://loopback.io">Loopback</a> was created as an open source mobile backend-as-a-service framework by
<a href="http://strongloop.com">Strongloop</a>. It allows you to setup a REST API in minutes and is based on
<a href="http://expressjs.com">Express</a>. </p>
<p>Here we will use version 3.4.0 to build an API for camping reservations:</p>
<p><img src="/img/loopback_datamodel.png" alt="DataModel" /></p>
<h2 id="getting_started">Getting Started</h2>
<p>Loopback comes with a <a href="https://loopback.io/doc/en/lb3/Command-line-tools.html">CLI tool</a> to generate an application.
You can configure everything manually, but the CLI tool is just a really handy gift to get you started. Install it via
the node package manager: <code>npm install -g loopback-cli</code></p>
<p>When installed, type <code>lb</code> to start the <a href="http://yeoman.io">yeoman-generator</a>:</p>
<pre><code> _-----_
| | ╭──────────────────────────╮
|--(o)--| │ Let's create a LoopBack │
`---------´ │ application! │
( _´U`_ ) ╰──────────────────────────╯
/___A___\ /
| ~ |
__'.___.'__
´ ` |° ´ Y `
? What's the name of your application? reservations
? Enter name of the directory to contain the project: reservations
create reservations/
info change the working directory to reservations
? Which version of LoopBack would you like to use? 3.x (current)
? What kind of application do you have in mind? empty-server (An empty LoopBack API, without any configured models or datasources)</code></pre>
<p>When finished, you will see the following project structure. The JSON files are for configuration and the Javascript
files are for extending Express.</p>
<pre><code>reservations/
├── client # Client JS, HTML and CSS files
│ └── README.md # Empty README.md file
├── package.json # Npm package specification
└── server # Node scripts and config
├── boot # Initialization scripts
│ └── root.js # Specify the contextroot
├── component-config.json # Loopback components config
├── config.json # Global settings
├── datasources.json # Datasource config
├── middleware.development.json # Middleware config for dev
├── middleware.json # Middleware config
├── model-config.json # Binds models to datasources
└── server.js # Main application script</code></pre>
<p>Go to the reservations folder and start the application by running npm start. Open http://localhost:3000/explorer in
your browser to see a basic <a href="http://swagger.io/swagger-ui/">Swagger-UI</a></p>
<h2 id="persistence">Persistence</h2>
<p>Look at server/datasources.json. We have no datasources configured yet. We will use an in-memory database here,
but there are many <a href="https://loopback.io/doc/en/lb3/Database-connectors.html">database connectors available</a> out-of-the-box.</p>
<p>Type <code>lb datasource</code> to start the generator again.</p>
<pre><code>? Enter the data-source name: reservationDS
? Select the connector for reservationDS: In-memory db (supported by StrongLoop)
Connector-specific configuration:
? window.localStorage key to use for persistence (browser only):
? Full path to file for persistence (server only): db.json</code></pre>
<p>The db.json file will persist the in-memory data to a file. This allows us to keep our data when we restart the server.
It also allows us to start our application with some test data.</p>
<p>We will create this file later on. Loopback won't give any warning when this file is not available yet.</p>
<h2 id="models">Models</h2>
<p>Loopback is designed around the concept of <a href="https://loopback.io/doc/en/lb3/Defining-models.html">models</a>. Let's create a model for our campground, <code>lb model</code></p>
<pre><code>? Enter the model name: campground
? Select the data-source to attach campground to: reservationDS (memory)
? Select model's base class PersistedModel
? Expose campground via the REST API? Yes
? Custom plural form (used to build REST URL):
? Common model or server only? server
Let's add some campground properties now.
Enter an empty property name when done.
? Property name: name
invoke loopback:property
? Property type: string
? Required? Yes
? Default value[leave blank for none]:
Let's add another campground property.
Enter an empty property name when done.
? Property name: location
invoke loopback:property
? Property type: geopoint
? Required? No
? Default value[leave blank for none]:
Let's add another campground property.
Enter an empty property name when done.
? Property name:</code></pre>
<p>We created the campground model and derived it from the PersistedModel, so we can save it to our
datasource. You can make a model common, which means that the same model can be shared between the client and the
server, but we decided to use it for the server only.</p>
<p>Our model has 2 properties, a name and a location.
The id property is automatically included, so you don’t have to add it.</p>
<p>Visit the API explorer at http://localhost:3000/explorer. You'll see a lot of endpoints available for our
campgrounds now.</p>
<p><img src="/img/loopback_explorer.png" alt="API Explorer" /></p>
<p>Let's test our API by getting all the campgrounds:</p>
<pre><code>curl -X GET 'http://localhost:3000/api/campgrounds'</code></pre>
<p>Since we don't have any data, the response will be an empty list. We now create the db.json file that we specified in
the previous section. Create a db.json file at the root of the project.</p>
<pre><code>{
&quot;ids&quot;: {
&quot;campground&quot;: 5
},
&quot;models&quot;: {
&quot;campground&quot;: {
&quot;1&quot;: &quot;{\&quot;name\&quot;:\&quot;Salt Lake City KOA\&quot;,\&quot;location\&quot;:{\&quot;lat\&quot;: 40.772112, \&quot;lng\&quot;: -111.932165},\&quot;id\&quot;:1}&quot;,
&quot;2&quot;: &quot;{\&quot;name\&quot;:\&quot;Gouldings Campground\&quot;,\&quot;location\&quot;:{\&quot;lat\&quot;: 37.006989, \&quot;lng\&quot;: -110.214907},\&quot;id\&quot;:2}&quot;,
&quot;3&quot;: &quot;{\&quot;name\&quot;:\&quot;Grand Canyon Mather Campground\&quot;,\&quot;location\&quot;:{\&quot;lat\&quot;: 36.056472, \&quot;lng\&quot;: -112.140728},\&quot;id\&quot;:3}&quot;,
&quot;4&quot;: &quot;{\&quot;name\&quot;:\&quot;Camping Paris Bois de Boulogne\&quot;,\&quot;location\&quot;:{\&quot;lat\&quot;: 48.868879, \&quot;lng\&quot;: 2.234914},\&quot;id\&quot;:4}&quot;
}
}
}</code></pre>
<p>Restart the server and try again. You should now see 4 campgrounds.
We will finish this part by generating our reservation model, lb model:</p>
<pre><code>? Enter the model name: reservation
? Select the data-source to attach reservation to: reservationDS (memory)
? Select model's base class PersistedModel
? Expose reservation via the REST API? Yes
? Custom plural form (used to build REST URL):
? Common model or server only? server
Let's add some reservation properties now.
Enter an empty property name when done.
? Property name: startDate
invoke loopback:property
? Property type: date
? Required? Yes
? Default value[leave blank for none]:
Let's add another reservation property.
Enter an empty property name when done.
? Property name: endDate
invoke loopback:property
? Property type: date
? Required? Yes
? Default value[leave blank for none]:
Let's add another reservation property.
Enter an empty property name when done.
? Property name:</code></pre>
<h2 id="relations">Relations</h2>
<p>Campgrounds can have zero or more reservations. We have to create a
<a href="https://loopback.io/doc/en/lb3/Define-model-relations.html">relationship between our models</a> to accomplish this, <code>lb
relation</code>:</p>
<pre><code>? Select the model to create the relationship from: campground
? Relation type: has many
? Choose a model to create a relationship with: reservation
? Enter the property name for the relation: reservations
? Optionally enter a custom foreign key:
? Require a through model? No</code></pre>
<p>Start the server again and go to the API Explorer. You will see some new endpoints for /campgrounds/{id}/reservations.</p>
<p>Let’s update our test data so we have a couple of reservations for our campgrounds.</p>
<pre><code>{
&quot;ids&quot;: {
&quot;campground&quot;: 5,
&quot;reservation&quot;: 3
},
&quot;models&quot;: {
&quot;campground&quot;: {
&quot;1&quot;: &quot;{\&quot;name\&quot;:\&quot;Salt Lake City KOA\&quot;,\&quot;location\&quot;:{\&quot;lat\&quot;: 40.772112, \&quot;lng\&quot;: -111.932165},\&quot;id\&quot;:1}&quot;,
&quot;2&quot;: &quot;{\&quot;name\&quot;:\&quot;Gouldings Campground\&quot;,\&quot;location\&quot;:{\&quot;lat\&quot;: 37.006989, \&quot;lng\&quot;: -110.214907},\&quot;id\&quot;:2}&quot;,
&quot;3&quot;: &quot;{\&quot;name\&quot;:\&quot;Grand Canyon Mather Campground\&quot;,\&quot;location\&quot;:{\&quot;lat\&quot;: 36.056472, \&quot;lng\&quot;: -112.140728},\&quot;id\&quot;:3}&quot;,
&quot;4&quot;: &quot;{\&quot;name\&quot;:\&quot;Camping Paris Bois de Boulogne\&quot;,\&quot;location\&quot;:{\&quot;lat\&quot;: 48.868879, \&quot;lng\&quot;: 2.234914},\&quot;id\&quot;:4}&quot;
},
&quot;reservation&quot;: {
&quot;1&quot;: &quot;{\&quot;startDate\&quot;:\&quot;2017-03-21\&quot;,\&quot;endDate\&quot;:\&quot;2017-03-23\&quot;,\&quot;campgroundId\&quot;:1,\&quot;id\&quot;:1}&quot;,
&quot;2&quot;: &quot;{\&quot;startDate\&quot;:\&quot;2017-03-25\&quot;,\&quot;endDate\&quot;:\&quot;2017-03-31\&quot;,\&quot;campgroundId\&quot;:2,\&quot;id\&quot;:2}&quot;
}
}
}</code></pre>
<h2 id="queries">Queries</h2>
<p>Loopback endpoints can also be used to <a href="https://loopback.io/doc/en/lb3/Querying-data.html">query specific data</a>.
Here’s a selection of what is possible out of the box:</p>
<ul>
<li>Show all campgrounds with ’KOA’ in there name
<code>/api/campgrounds?filter[where][name][like]=KOA</code></li>
<li>Show all reservations after or on 2017–03–22
<code>/api/reservations?filter[where][startDate][gte]=2017-03-22</code></li>
<li>Show only the names of the campgrounds:
<code>/api/campgrounds?filter[fields][name]=true</code></li>
<li>Show everything but the names of the campgrounds:
<code>/api/campgrounds?filter[fields][name]</code></li>
<li>Show campgrounds and include their reservations:
<code>/api/campgrounds?filter[include][reservations]</code></li>
<li>Show the first 2 campgrounds:
<code>/api/campgrounds?filter[limit]=2</code></li>
<li>Show the next 2 campgrounds:
<code>/api/campgrounds?filter[skip]=2&amp;filter[limit]=2</code></li>
<li>Order campgrounds by name
<code>/api/campgrounds?filter[order]=name</code></li>
<li>Descending order campgrounds by name:
<code>/api/campgrounds?filter[order]=name%20DESC</code></li>
</ul>
<h2 id="geolocation">Geolocation</h2>
<p>You can also query based on geolocation. The location of our campground is a
<a href="https://loopback.io/doc/en/lb3/Geotype.html">geopoint</a>.
Imagine we are at Arches Nation Park and we want to search for all campgrounds within a 200 mile radius:</p>
<pre><code>/api/campgrounds?filter[where][location][near]=38.7006538,-109.5643742&amp;filter[where][location][maxDistance]=200</code></pre>
<h2 id="validation">Validation</h2>
<p>When we created the campground model, we made the name required. If we try to create a campground without a name,
loopback will give us a validation error saying that the field cannot be blank. </p>
<p>Loopback also has some built-in
<a href="https://loopback.io/doc/en/lb2/Validating-model-data.html#using-validation-methods">validation methods</a> for frequently
used validation. The name of our campground should have max. 100 characters. We can implement this by adding the
following code to server/models/campground.js:</p>
<pre><code>'use strict';
module.exports = function(Campground) {
Campground.validatesLengthOf('name', {max: 100, message: {max: 'Name is too long'}});
};</code></pre>
<p>You can also add custom validation. For a reservation, the endDate should be after the startDate:</p>
<pre><code>'use strict';
module.exports = function(Reservation) {
Reservation.validate('startDate', dateValidator, {message: 'endDate should be after startDate'});
function dateValidator(err) {
if(this.startDate &gt;= this.endDate) {
err();
}
}
};</code></pre>
<h2 id="security">Security</h2>
<p>All our endpoints are public. We should add some security here. Our example will have 3 types of users. Anonymous
users should be able to see all campgrounds. When they register, they will become customers who can make reservations
and can see only there own reservations.</p>
<p>Administrators should be able to see and do anything. Loopback has
<a href="http://loopback.io/doc/en/lb3/Using-built-in-models.html">built-in models</a> for security, but it is advised not to use
them directly. We will create a customer model that extends from the User model. <code>lb model</code>:</p>
<pre><code>? Enter the model name: customer
? Select the data-source to attach customer to: reservationDS (memory)
? Select model's base class User
? Expose customer via the REST API? Yes
? Custom plural form (used to build REST URL):
? Common model or server only? server
Let's add some customer properties now.
Enter an empty property name when done.
? Property name: name
invoke loopback:property
? Property type: string
? Required? No
? Default value[leave blank for none]:
Let's add another customer property.
Enter an empty property name when done.
? Property name:</code></pre>
<p>Customers can have zero or more reservations. Let's create a relation here, <code>lb relation</code>:</p>
<pre><code>? Select the model to create the relationship from: customer
? Relation type: has many
? Choose a model to create a relationship with: reservation
? Enter the property name for the relation: reservations
? Optionally enter a custom foreign key:
? Require a through model? No</code></pre>
<p>Since we started from an empty server, the built-in models are not defined in server/model-config.json. Let's add those
now.</p>
<p>Setting the 'public' property to false means that they will not be public and will not be shown in our API explorer.</p>
<pre><code>{
&quot;_meta&quot;: {
&quot;sources&quot;: [
&quot;loopback/common/models&quot;,
&quot;loopback/server/models&quot;,
&quot;../common/models&quot;,
&quot;./models&quot;
],
&quot;mixins&quot;: [
&quot;loopback/common/mixins&quot;,
&quot;loopback/server/mixins&quot;,
&quot;../common/mixins&quot;,
&quot;./mixins&quot;
]
},
&quot;campground&quot;: {
&quot;dataSource&quot;: &quot;reservationDS&quot;,
&quot;public&quot;: true
},
&quot;reservation&quot;: {
&quot;dataSource&quot;: &quot;reservationDS&quot;,
&quot;public&quot;: true
},
&quot;customer&quot;: {
&quot;dataSource&quot;: &quot;reservationDS&quot;,
&quot;public&quot;: true
},
&quot;User&quot;: {
&quot;dataSource&quot;: &quot;reservationDS&quot;,
&quot;public&quot;: false
},
&quot;AccessToken&quot;: {
&quot;dataSource&quot;: &quot;reservationDS&quot;,
&quot;public&quot;: false
},
&quot;ACL&quot;: {
&quot;dataSource&quot;: &quot;reservationDS&quot;,
&quot;public&quot;: false
},
&quot;RoleMapping&quot;: {
&quot;dataSource&quot;: &quot;reservationDS&quot;,
&quot;public&quot;: false
},
&quot;Role&quot;: {
&quot;dataSource&quot;: &quot;reservationDS&quot;,
&quot;public&quot;: false
}
}</code></pre>
<p>We will now add 3 users to application:</p>
<ul>
<li>Andy, our administrator : (username: andy, password: andy)</li>
<li>Kenneth, a customer : (username: kenneth, password: kenneth)</li>
<li>Claudiu, another customer: (username: claudiu, password: claudiu)</li>
</ul>
<p>The passwords need to be hashed in the db.json file. We also link our reservations to our
customers, by adding customerId to the reservation table.</p>
<pre><code>{
&quot;ids&quot;: {
&quot;campground&quot;: 5,
&quot;reservation&quot;: 3,
&quot;customer&quot;: 4,
&quot;AccessToken&quot;: 1,
&quot;ACL&quot;: 1,
&quot;RoleMapping&quot;: 1,
&quot;Role&quot;: 1
},
&quot;models&quot;: {
&quot;campground&quot;: {
&quot;1&quot;: &quot;{\&quot;name\&quot;:\&quot;Salt Lake City KOA\&quot;,\&quot;location\&quot;:{\&quot;lat\&quot;: 40.772112, \&quot;lng\&quot;: -111.932165},\&quot;id\&quot;:1}&quot;,
&quot;2&quot;: &quot;{\&quot;name\&quot;:\&quot;Gouldings Campground\&quot;,\&quot;location\&quot;:{\&quot;lat\&quot;: 37.006989, \&quot;lng\&quot;: -110.214907},\&quot;id\&quot;:2}&quot;,
&quot;3&quot;: &quot;{\&quot;name\&quot;:\&quot;Grand Canyon Mather Campground\&quot;,\&quot;location\&quot;:{\&quot;lat\&quot;: 36.056472, \&quot;lng\&quot;: -112.140728},\&quot;id\&quot;:3}&quot;,
&quot;4&quot;: &quot;{\&quot;name\&quot;:\&quot;Camping Paris Bois de Boulogne\&quot;,\&quot;location\&quot;:{\&quot;lat\&quot;: 48.868879, \&quot;lng\&quot;: 2.234914},\&quot;id\&quot;:4}&quot;
},
&quot;reservation&quot;: {
&quot;1&quot;: &quot;{\&quot;startDate\&quot;:\&quot;2017-03-21\&quot;,\&quot;endDate\&quot;:\&quot;2017-03-23\&quot;,\&quot;campgroundId\&quot;:1,\&quot;customerId\&quot;:2,\&quot;id\&quot;:1}&quot;,
&quot;2&quot;: &quot;{\&quot;startDate\&quot;:\&quot;2017-03-25\&quot;,\&quot;endDate\&quot;:\&quot;2017-03-31\&quot;,\&quot;campgroundId\&quot;:2,\&quot;customerId\&quot;:3,\&quot;id\&quot;:2}&quot;
},
&quot;customer&quot;: {
&quot;1&quot;: &quot;{\&quot;name\&quot;:\&quot;Andy Van Den Heuvel\&quot;,\&quot;username\&quot;:\&quot;andy\&quot;,\&quot;password\&quot;:\&quot;$2a$10$1lmPRI0Xjd5fU8HGdPmDoOkZpIPJj2axcdJYIfc/3RUnBDDqQe31K\&quot;,\&quot;email\&quot;:\&quot;andy@optis.be\&quot;,\&quot;id\&quot;:1}&quot;,
&quot;2&quot;: &quot;{\&quot;name\&quot;:\&quot;Kenneth Van den Berghe\&quot;,\&quot;username\&quot;:\&quot;kenneth\&quot;,\&quot;password\&quot;:\&quot;$2a$10$H5wtnFvhxf8CPn66gEbPu.tki2WRpkplqvUV3yhQ049ugY8rHFSJi\&quot;,\&quot;email\&quot;:\&quot;kenneth@optis.be\&quot;,\&quot;id\&quot;:2}&quot;,
&quot;3&quot;: &quot;{\&quot;name\&quot;:\&quot;Claudiu Matei\&quot;,\&quot;username\&quot;:\&quot;claudiu\&quot;,\&quot;password\&quot;:\&quot;$2a$10$6b9jxIwb6y84gpq.ZU57YegRM4BWxHoXc.K/WwlEOJTa/9fO7cCta\&quot;,\&quot;email\&quot;:\&quot;claudiu@optis.be\&quot;,\&quot;id\&quot;:3}&quot;
},
&quot;AccessToken&quot;: {},
&quot;ACL&quot;: {},
&quot;RoleMapping&quot;: {
&quot;1&quot;: &quot;{\&quot;principalType\&quot;:\&quot;USER\&quot;,\&quot;principalId\&quot;:\&quot;1\&quot;,\&quot;roleId\&quot;:1,\&quot;id\&quot;:1}&quot;
},
&quot;Role&quot;: {
&quot;1&quot;: &quot;{\&quot;name\&quot;:\&quot;admin\&quot;,\&quot;created\&quot;:\&quot;2017-02-21T06:07:25.571Z\&quot;,\&quot;modified\&quot;:\&quot;2017-02-21T06:07:25.571Z\&quot;,\&quot;id\&quot;:1}&quot;
}
}
}</code></pre>
<p>We will now activate our authentication. To do this, we must add a
<a href="https://loopback.io/doc/en/lb3/Defining-boot-scripts.html">bootscript</a>. Create a new file
server/boot/authentication.js with the following content and restart your server afterwards.</p>
<pre><code>'use strict';
module.exports = function enableAuthentication(server) {
server.enableAuth();
};</code></pre>
<p>Authentication is now enabled, but all endpoints are still public because we haven't configured any authorization.
Loopback uses <a href="https://loopback.io/doc/en/lb3/Controlling-data-access.html">access control lists</a> for this. Let's add
some rules here.</p>
<p>First deny all access to everybody, <code>lb acl</code>:</p>
<pre><code>? Select the model to apply the ACL entry to: (all existing models)
? Select the ACL scope: All methods and properties
? Select the access type: All (match all types)
? Select the role All users
? Select the permission to apply Explicitly deny access</code></pre>
<p>Now allow everybody to view the campgrounds, <code>lb acl</code></p>
<pre><code>? Select the model to apply the ACL entry to: campground
? Select the ACL scope: All methods and properties
? Select the access type: Read
? Select the role All users
? Select the permission to apply Explicitly grant access
</code></pre>
<p>Also, allow every customer to their own info, <code>lb acl</code>:</p>
<pre><code>? Select the model to apply the ACL entry to: customer
? Select the ACL scope: All methods and properties
? Select the access type: All (match all types)
? Select the role The user owning the object
? Select the permission to apply Explicitly grant access</code></pre>
<p>And as a last rule, allow administrators to do and see all, <code>lb acl</code>:</p>
<pre><code>? Select the model to apply the ACL entry to: (all existing models)
? Select the ACL scope: All methods and properties
? Select the access type: All (match all types)
? Select the role other
? Enter the role name: admin
? Select the permission to apply Explicitly grant access</code></pre>
<p>Start your server and go to http://localhost:3000/api/campgrounds. As an anonymous user I can still see all campgrounds.
When I go to http://localhost:3000/api/reservations, I get a 401 Authorization Required.</p>
<p>We will now log in as Kenneth to see his reservations. Go to http://localhost:3000/explorer/#!/customer/customer_login
and log in:</p>
<p><img src="/img/loopback_login.png" alt="Login" /></p>
<p>The id of the response is a generated access token. You can now copy it and set it in the header to provide the access
token for all calls in the API Explorer.</p>
<p><img src="/img/loopback_token.png" alt="Login" /></p>
<p>Or you can add the access token as a request parameter. Let's try this out.</p>
<ul>
<li>As Kenneth, I can see my own reservations: <code>/api/customers/2/reservations?access_token=XMFN5GsykpxFokvWsXRYtKZidlJYKyClvak0KmEn87LisnFYSQ9TzmrBcz9GFrHv</code></li>
<li>Asking Claudiu's reservations results in an 401 unauthorized: <code>/api/customers/3/reservations?access_token=XMFN5GsykpxFokvWsXRYtKZidlJYKyClvak0KmEn87LisnFYSQ9TzmrBcz9GFrHv</code></li>
<li>As Claudiu, I can see my own reservation: <code>/api/customers/3/reservations?access_token=v51y2iZa1nkKTWC7s1yKELaIatfDJPVxcEEVa6FFIG4llZCGyZVbwR4plhfpYAxx</code></li>
<li>But I can't see Kenneth's reservations: <code>/api/customers/2/reservations?access_token=v51y2iZa1nkKTWC7s1yKELaIatfDJPVxcEEVa6FFIG4llZCGyZVbwR4plhfpYAxx</code></li>
<li>As Andy, I can see Kenneth's reservations: <code>/api/customers/2/reservations?access_token=okxVkWcdoVzWb3WmCK9KkiuBArz1HOOHrIn1h2mOfa0kBzeUna1V9wFmFRe6BCHe</code></li>
<li>And also Claudiu's reservations: <code>/api/customers/3/reservations?access_token=okxVkWcdoVzWb3WmCK9KkiuBArz1HOOHrIn1h2mOfa0kBzeUna1V9wFmFRe6BCHe</code></li>
<li>As Andy you can also see all reservations: <code>/api/reservations?access_token=okxVkWcdoVzWb3WmCK9KkiuBArz1HOOHrIn1h2mOfa0kBzeUna1V9wFmFRe6BCHe</code></li>
</ul>
<h2 id="application_logic">Application logic</h2>
<p>Up to this point, we were able to build a fully functioning REST API, mostly from generating code. But more advanced
applications will almost always need some extra application logic.</p>
<p>Loopback allows you to add remote methods, remote hooks and operation hooks. Remote hooks and operation hooks are
practically the same. The difference is that operational hooks are more high-level defined, whereas remote hooks are
more specific to an endpoint.</p>
<p>In our example, we want to send a verification email when a customer has made a reservation. Loopback has an [email
connector](https://loopback.io/doc/en/lb3/Email-connector.html) available based on <a href="https://nodemailer.com">Nodemailer</a></p>
<p>Go to server/datasources.json and add the email configuration. I used gmail for this:</p>
<pre><code>{
&quot;reservationDS&quot;: {
&quot;name&quot;: &quot;reservationDS&quot;,
&quot;localStorage&quot;: &quot;&quot;,
&quot;file&quot;: &quot;db.json&quot;,
&quot;connector&quot;: &quot;memory&quot;
},
&quot;emailDS&quot;: {
&quot;name&quot;: &quot;mail&quot;,
&quot;connector&quot;: &quot;mail&quot;,
&quot;transports&quot;: [{
&quot;type&quot;: &quot;SMTP&quot;,
&quot;host&quot;: &quot;smtp.gmail.com&quot;,
&quot;secure&quot;: true,
&quot;port&quot;: 465,
&quot;auth&quot;: {
&quot;user&quot;: &quot;YOUR_USER&quot;,
&quot;pass&quot;: &quot;YOUR_PASSWORD&quot;
}
}]
}
}</code></pre>
<p>Now we bind the datasource in server/models-config.js:</p>
<pre><code>{
&quot;_meta&quot;: {
&quot;sources&quot;: [
&quot;loopback/common/models&quot;,
&quot;loopback/server/models&quot;,
&quot;../common/models&quot;,
&quot;./models&quot;
],
&quot;mixins&quot;: [
&quot;loopback/common/mixins&quot;,
&quot;loopback/server/mixins&quot;,
&quot;../common/mixins&quot;,
&quot;./mixins&quot;
]
},
&quot;campground&quot;: {
&quot;dataSource&quot;: &quot;reservationDS&quot;,
&quot;public&quot;: true
},
&quot;reservation&quot;: {
&quot;dataSource&quot;: &quot;reservationDS&quot;,
&quot;public&quot;: true
},
&quot;customer&quot;: {
&quot;dataSource&quot;: &quot;reservationDS&quot;,
&quot;public&quot;: true
},
&quot;User&quot;: {
&quot;dataSource&quot;: &quot;reservationDS&quot;,
&quot;public&quot;: false
},
&quot;AccessToken&quot;: {
&quot;dataSource&quot;: &quot;reservationDS&quot;,
&quot;public&quot;: false
},
&quot;ACL&quot;: {
&quot;dataSource&quot;: &quot;reservationDS&quot;,
&quot;public&quot;: false
},
&quot;RoleMapping&quot;: {
&quot;dataSource&quot;: &quot;reservationDS&quot;,
&quot;public&quot;: false
},
&quot;Role&quot;: {
&quot;dataSource&quot;: &quot;reservationDS&quot;,
&quot;public&quot;: false
},
&quot;Email&quot;: {
&quot;dataSource&quot;: &quot;emailDS&quot;
}
}</code></pre>
<p>Go to server/models/reservation.js and add the logic to send and email after save:</p>
<pre><code>'use strict';
module.exports = function (Reservation) {
Reservation.validate('startDate', dateValidator, {message: 'endDate should be after startDate'});
function dateValidator(err) {
if (this.startDate &gt;= this.endDate) {
err();
}
}
Reservation.observe(&quot;after save&quot;, function (ctx, next) {
Reservation.app.models.Campground.findById(ctx.instance.campgroundId, function (err, campground) {
Reservation.app.models.Email.send({
to: 'andy@optis.be',
from: 'noreply@optis.be',
subject: 'Thank you for your reservation at ' + campground.name,
html: '&lt;p&gt;We confirm your reservation for &lt;strong&gt;' + campground.name + '&lt;/strong&gt;&lt;/p&gt;'
}, function (err, mail) {
console.log('email sent!');
});
});
next();
});
};</code></pre>
<h2 id="storage">Storage</h2>
<p>If you are interested in uploading/downloading files from your API, you can use Loopback's
<a href="https://loopback.io/doc/en/lb3/Storage-component.html">Storage Component</a>. For this, you need to install the storage
component via NPM:</p>
<pre><code>npm install loopback-component-storage --save</code></pre>
<p>Just like with emails, the storage component is called a datasource. Let's add it to server/datasources.json</p>
<pre><code>{
&quot;reservationDS&quot;: {
&quot;name&quot;: &quot;reservationDS&quot;,
&quot;localStorage&quot;: &quot;&quot;,
&quot;file&quot;: &quot;db.json&quot;,
&quot;connector&quot;: &quot;memory&quot;
},
&quot;emailDS&quot;: {
&quot;name&quot;: &quot;mail&quot;,
&quot;connector&quot;: &quot;mail&quot;,
&quot;transports&quot;: [{
&quot;type&quot;: &quot;SMTP&quot;,
&quot;host&quot;: &quot;smtp.gmail.com&quot;,
&quot;secure&quot;: true,
&quot;port&quot;: 465,
&quot;auth&quot;: {
&quot;user&quot;: &quot;YOUR_USER&quot;,
&quot;pass&quot;: &quot;YOUR_PASSWORD&quot;
}
}]
},
&quot;photos&quot;: {
&quot;name&quot;: &quot;photos&quot;,
&quot;connector&quot;: &quot;loopback-component-storage&quot;,
&quot;provider&quot;: &quot;filesystem&quot;,
&quot;root&quot;: &quot;./server/files&quot;
}
}</code></pre>
<p>We use our local filesystem as the provider here, but the storage component uses
<a href="https://github.com/pkgcloud/pkgcloud">pkgcloud</a> to support multiple cloud providers (Amazon, Azure, Google, HP,
Openstack, Rackspace)</p>
<p>Loopback keeps files in containers. We have to make a container model so we can create a container for our photos,
<code>lb model</code>:</p>
<pre><code>? Enter the model name: container
? Select the data-source to attach container to: photos (loopback-component-storage)
? Select model's base class Model
? Expose container via the REST API? Yes
? Custom plural form (used to build REST URL):
? Common model or server only? server
Let's add some container properties now.
Enter an empty property name when done.
? Property name:</code></pre>
<p>Create the server/files/photos folder, so we can upload some photos to it and start your server. When you go to the
API explorer, you can see the /containers endpoint, but we haven't created any containers yet. For this part, we are
going to use curl.</p>
<p>Create the container 'photos':</p>
<pre><code>curl -X GET --header 'Accept: application/json' 'http://localhost:3000/api/containers/photos'</code></pre>
<p>Upload to the 'photos'-container:</p>
<pre><code>curl -F &quot;image=@image.jpg&quot; http://localhost:3000/api/containers/photos/upload</code></pre>
<p>Download from the 'photos'-container:</p>
<pre><code>http://localhost:3000/api/containers/photos/download/image.jpg</code></pre>
<h2 id="testing">Testing</h2>
<p>We are almost ready, but we want to ensure we can test our endpoints.
We will use <a href="http://mochajs.org">mocha</a>, <a href="http://chaijs.com">chai</a> and <a href="http://chaijs.com/plugins/chai-http">chai-http</a>
for this.</p>
<p>If you haven't mocha on your machine. Install it globally via npm</p>
<pre><code>npm install -g mocha</code></pre>
<p>Then install chai and chai-http for the project:</p>
<pre><code>npm install chai chai-http --save-dev</code></pre>
<p>We can now write a test at test/campground.js. I haven't written the complete testsuite here, just a few tests so you
get the idea. chai will make sure that the server is started before we do the request, and stopped after the test.</p>
<pre><code>'use strict';
var chai = require('chai');
var chaiHttp = require('chai-http');
var server = require('../server/server');
var should = chai.should();
chai.use(chaiHttp);
describe('Campgrounds', function() {
it('should show all campgrounds on GET /api/campgrounds', function(done) {
chai.request(server)
.get('/api/campgrounds')
.end(function(err, res) {
res.should.have.status(200);
res.body.should.have.lengthOf(4);
done();
});
});
it('should show only the names of the campgrounds on GET /api/campgrounds?filter[fields][name]=true', function(done) {
chai.request(server)
.get('/api/campgrounds?filter[fields][name]=true')
.end(function(err, res) {
res.should.have.status(200);
res.body[0].should.have.property('name');
res.body[0].should.not.have.property('id');
done();
});
});
it('should show the first 2 campgrounds on GET /api/campgrounds?filter[limit]=2', function(done) {
chai.request(server)
.get('/api/campgrounds?filter[limit]=2')
.end(function(err, res) {
res.should.have.status(200);
res.body.should.have.lengthOf(2);
res.body[0].name.should.equal('Salt Lake City KOA');
res.body[1].name.should.equal('Gouldings Campground');
done();
});
});
it('should show the last 2 campgrounds on GET /api/campgrounds?filter[skip]=2&amp;filter[limit]=2', function(done) {
chai.request(server)
.get('/api/campgrounds?filter[skip]=2&amp;filter[limit]=2')
.end(function(err, res) {
res.should.have.status(200);
res.body.should.have.lengthOf(2);
res.body[0].name.should.equal('Grand Canyon Mather Campground');
res.body[1].name.should.equal('Camping Paris Bois de Boulogne');
done();
});
});
});</code></pre>
<p>Run the command <code>mocha</code> now and you should see all tests passing:</p>
<p><img src="/img/loopback_testing.png" alt="testing" /></p>
<p>Ok, we're almost done now. Last stop: Deploy to Production.</p>
<h2 id="deployment">Deployment</h2>
<p>In order to go to production, we need some
<a href="https://loopback.io/doc/en/lb3/Environment-specific-configuration.html">Environment-specific configuration</a>.</p>
<p>Loopback has naming conventions for this. For security reasons, we don't want to show the API explorer when running in
production. Create a file named /server/component-config.prod.json</p>
<pre><code>{
&quot;loopback-component-explorer&quot;: null
}</code></pre>
<p>We also don't want to work against an in-memory database. We will switch to mongoDB here.
We will have to install the <a href="https://loopback.io/doc/en/lb3/MongoDB-connector.html">MongoDB Connector</a> for it:</p>
<pre><code>npm install loopback-connector-mongodb --save</code></pre>
<p>I will use an <a href="https://mlab.com">MLab</a>-hosted MongoDB. You can sign up and create a free sandbox here too.</p>
<ul>
<li>Create a database 'reservations'</li>
<li>The username credentials you used to signup for MLab are not used to connect in your application, make sure you
create a database user here:</li>
</ul>
<p><img src="/img/loopback_mlab.png" alt="MLab" /></p>
<p>Create a new file called server/datasources.prod.json and add your mongodb settings:</p>
<pre><code>{
&quot;reservationDS&quot;: {
&quot;host&quot;: &quot;YOUR_HOST&quot;,
&quot;port&quot;: 0,
&quot;url&quot;: false,
&quot;database&quot;: &quot;reservations&quot;,
&quot;name&quot;: &quot;reservations&quot;,
&quot;connector&quot;: &quot;mongodb&quot;,
&quot;user&quot;: &quot;YOUR_USERNAME&quot;,
&quot;password&quot;: &quot;YOUR_PASSWORD&quot;
}
}</code></pre>
<p>Notice dat we use the naming pattern componenet-config.env.json and datasources.env.json here.
Loopback uses NODE_ENV to decide what config should be loaded. Let's change our environment to prod.</p>
<pre><code>export NODE_ENV=&quot;prod&quot;</code></pre>
<p>Loopback will now use our new configuration. Our explorer is disabled and we can still use are api, this time through
MongoDB:</p>
<pre><code>http://localhost:3000/api/campgrounds</code></pre>
<p>Since we are working on mongoDB now, we don't have any test data available anymore. Let's create a collection
'campground' and when you click on the collection, you can add a new document.</p>
<p>You can do all of this inside the mLab web interface. E.g:</p>
<pre><code>{
&quot;name&quot;: &quot;Salt Lake City KOA&quot;,
&quot;location&quot;:{
&quot;lat&quot;: 40.772112,
&quot;lng&quot;: -111.932165
}
}</code></pre>
<p>When you created the document and go to http://localhost:3000/api/campgrounds and you should see the newly created.</p>
<h2 id="source_code">Source code</h2>
<p><a href="https://github.com/codeaddslife/loopback">https://github.com/codeaddslife/loopback</a></p></description>
</item>
<item>
<title>Progressive web apps</title>
<link>https://www.codeaddslife.com/articles/progressive-web-apps.html</link>
<description><h1 id="progressive_web_apps">Progressive web apps</h1>
<p>The term <a href="https://developers.google.com/web/progressive-web-apps/">Progressive web apps</a> was coined in
<a href="https://infrequently.org/2015/06/progressive-apps-escaping-tabs-without-losing-our-soul/">june 2015</a> by Alex Russell
and Frances Berriman to bridge the gap between web apps and native apps. In this article, we will create a fully
functioning progressive web app to show the main concepts in more detail.</p>
<h2 id="what_are_progressive_web_apps?">What are progressive web apps?</h2>
<p>Progressive web apps (PWA) are web applications that <em>progress over time</em>. If you happen to visit a website a lot, it
can be great to cache some of these assets offline or to install it on the homescreen.</p>
<p>It is build on the foundation of <a href="https://alistapart.com/article/understandingprogressiveenhancement">progressive enhancement</a> and
<a href="https://www.smashingmagazine.com/2011/01/guidelines-for-responsive-web-design">responsive web design</a>. Like any modern
website, it should work for anybody anywhere and use more modern browser features when they are available.</p>
<p>Progressive web apps differ from hybrid apps as they are not installed via an app store (A process where you typically
<a href="http://blog.gaborcselle.com/2012/10/every-step-costs-you-20-of-users.html">loose a lot of potential users</a>).</p>
<p>They are updated on the server, which costs less to maintain since all users are on the same version. PWA's rely heaviliy on service workers to provide
a native app-like experience.</p>
<h2 id="example">Example</h2>
<p>In this article, we will build a progressive web app for my hometown's
<a href="https://www.velo-antwerpen.be/en">bike renting service Velo</a>. A lot of cities have such a renting service nowadays as
part of their public transport system. In our app, we want to show the availability of bikes per station (And also show the closest
station, if you wanted to hop onto a bike right away).</p>
<p><img src="/img/pwa_velo.jpg" alt="velo" /></p>
<p>I used <a href="vuejs.org">Vue.js</a> for this. This application is kept as simple as possible to focus on the PWA-specific parts.
For a real world Vue.js application, you might want to check out Vue's <a href="https://github.com/vuejs/vue-cli">CLI tool</a>.</p>
<h2 id="getting_started">Getting Started</h2>
<p>First off, we start with a basic web application. The index.html-file will look like this:</p>
<pre><code>&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;head&gt;
&lt;title&gt;MyBike&lt;/title&gt;
&lt;link rel=&quot;icon&quot; type=&quot;image/png&quot; href=&quot;img/favicon.png&quot;&gt;
&lt;link rel=&quot;stylesheet&quot; href=&quot;main.css&quot; media=&quot;all&quot; /&gt;
&lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1&quot;&gt;
&lt;meta name=&quot;theme-color&quot; content=&quot;#ffffff&quot;&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;div id=&quot;app&quot;&gt;
&lt;div id=&quot;logo&quot;&gt;
&lt;img src=&quot;img/logo.png&quot; alt=&quot;MyBike&quot; /&gt;
&lt;/div&gt;
&lt;ul class=&quot;stations&quot;&gt;
&lt;li v-for=&quot;station in stations&quot;&gt;
&lt;div class=&quot;station&quot;&gt;
&lt;div class=&quot;status&quot;&gt;
&lt;img v-if=&quot;station.bikes == station.slots&quot; src=&quot;img/status-full.png&quot; alt=&quot;{{station.address}}&quot; /&gt;
&lt;img v-else-if=&quot;station.bikes &gt; 0&quot; src=&quot;img/status-available.png&quot; alt=&quot;{{station.address}}&quot; /&gt;
&lt;img v-else-if=&quot;station.bikes == 0&quot; src=&quot;img/status-empty.png&quot; alt=&quot;{{station.address}}&quot; /&gt;
&lt;img v-else-if=&quot;station.status != OPN&quot; src=&quot;img/status-closed.png&quot; alt=&quot;{{station.address}}&quot; /&gt;
&lt;/div&gt;
&lt;strong&gt;{{station.address}} {{station.addressNumber}}&lt;/strong&gt;
&lt;div&gt;Bikes: {{station.bikes}} - Slots {{station.slots}}&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;script src=&quot;js/axios.min.js&quot;&gt;&lt;/script&gt;
&lt;script src=&quot;js/vue.min.js&quot;&gt;&lt;/script&gt;
&lt;script src=&quot;js/app.js&quot;&gt;&lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;</code></pre>
<p>As you can see we load vue.js here, which will load a view for us based on the data that is coming back from
our http request. To keep things simple, this data is a local JSON file.</p>
<pre><code>var app = new Vue({
el: '#app',
data: {
stations: []
},
beforeMount: function () {
this.loadStations();
},
methods: {
loadStations: function () {
var self = this;
axios.get('/stations.json')
.then(function (response) {
self.stations = response.data;
})
.catch(function (error) {
console.log(error);
});
}
}
});</code></pre>
<p>We will also have some CSS for a basic design and to make this app responsive.</p>
<pre><code>body { color: #242729; font: 15px/1.3 Arial,&quot;Helvetica Neue&quot;,Helvetica,sans-serif; margin: 40px 0; }
small { color: #999; }
#app { margin: 0 auto; max-width: 700px; }
#logo { margin-bottom: 40px; text-align: center; }
#logo img { width: 100px; height: auto; }
ul.stations { list-style: none; margin: 0 20px; padding: 0; position: relative; }
ul.stations li { border-bottom: 1px solid #e4e6e8; }
ul.stations li div.station { margin-bottom: 10px; margin-top: 10px; margin-left: 58px; }
ul.stations li div.station:after { content: ''; display: block; clear: both; }
ul.stations li div.station div.status { border-radius: 50%; float: left; margin-left: -58px; width: 48px; }
ul.stations li div.station div.status img { width: 48px; }
div.info {background-color: #d9edf7; color: #31708f; border-radius: 16px; word-wrap: break-word; padding: 20px; margin-bottom: 20px;}</code></pre>
<p>To see the example for yourself, type the following commands:</p>
<pre><code>npm install -g browser-sync
npm start</code></pre>
<p>The app will now render beautifully on the different browsers.</p>
<p><img src="/img/pwa_getting_started.png" alt="getting started" /></p>
<h1 id="service_workers">Service workers</h1>
<p><a href="https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API">Service workers</a> are a
big part of PWAs. They allow you to develop <a href="http://offlinefirst.org/">offline first</a>.
A service worker is a javascript process that runs separately and acts as a programmable proxy
between your application and the network. To avoid
<a href="https://en.wikipedia.org/wiki/Man-in-the-middle_attack">man-in-the-middle attacks</a>, service workers
have to run over https.</p>
<p>In order to notify the browser that you have a service worker available, you have to register it on load.
We do this in the our <code>app.js</code> file. We also check if serviceWorker is a know feature in our browser, so when it's not,
the app will not crash.</p>
<pre><code>if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js').then(function() {
console.log('ServiceWorker registration successful');
}).catch(function(err) {
console.log('ServiceWorker registration failed: ', err);
});
}</code></pre>
<p>If you look at the code, you see that we register a seperate javascript file here. This is necassary as the service worker will run
seperate from your other javascript code. The service worker also has no access to your DOM. <code>sw.js</code> should be created at the root-level, so
not inside the js-folder.</p>
<p>Let's create <code>sw.js</code>:</p>
<pre><code class="language-javascript">var CACHE_NAME = 'my-bike-v1 ';
var CACHED_URLS = [
'/',
'/img/favicon.png',
'/img/icon-120.png',
'/img/icon-144.png',
'/img/icon-152.png',
'/img/icon-192.png',
'/img/icon-384.png',
'/img/logo.png',
'/img/status-available.png',
'/img/status-closed.png',
'/img/status-empty.png',
'/img/status-full.png',
'/js/axios.min.js',
'/js/vue.min.js',
'/js/app.js',