Skip to content

Instantly share code, notes, and snippets.

@agungf
Forked from bajtos/notice.md
Last active August 29, 2015 14:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save agungf/c15be1a6b8b4eb412f43 to your computer and use it in GitHub Desktop.
Save agungf/c15be1a6b8b4eb412f43 to your computer and use it in GitHub Desktop.

Build a full-stack application with LoopBack and AngularJS

LoopBack logo AngularJS logo

In this workshop, you will learn how to build a full-stack Whiskey-voting application using LoopBack and AngularJS.

Contents

Table of Contents generated with DocToc

Scaffold an API server in 5 minutes

  1. Start by installing LoopBack's Yeoman generator:
$ npm install -g generator-loopback
  1. Create and switch to your project's directory:
$ mkdir whiskey
$ cd whiskey
  1. Scaffold a loopback application:
$ yo loopback
  1. Add a "Whiskey" model with the properties "name" (string), "distillery" (string), "imageUrl" (string):
$ yo loopback:model Whiskey
[?] Enter the model name: Whiskey
[?] Select the data-source to attach Whiskey to: db (memory)
[?] Expose Whiskey via the REST API? Yes
[?] Custom plural form (used to build REST URL):
Let's add some Whiskey properties now.

Enter an empty property name when done.
[?] Property name: name
   invoke   loopback:property
[?] Property type: string
[?] Required? Yes

Let's add another Whiskey property.
Enter an empty property name when done.
[?] Property name: distillery
   invoke   loopback:property
[?] Property type: string
[?] Required? Yes

Let's add another Whiskey property.
Enter an empty property name when done.
[?] Property name: imageUrl
   invoke   loopback:property
[?] Property type: string
[?] Required? Yes

Let's add another Whiskey property.
Enter an empty property name when done.
[?] Property name:
  1. Add a "Review" model with the properties "rating" (number) and "comment" (string).

  2. Setup the relation a "Whiskey" has many "reviews":

yo loopback:relation
[?] Select the model to create the relationship from: Whiskey
[?] Relation type: has many
[?] Choose a model to create a relationship with: Review
[?] Enter the property name for the relation: reviews
[?] Optionally enter a custom foreign key:
  1. Start your API server
$ node .

Explore the API

  1. Open LoopBack Explorer in your favourite browser:
$ open http://localhost:3000/explorer

LoopBack Explorer

  1. Create a Whiskey entry using the Explorer:
POST /Whiskeys
{
  "name": "Green Spot 12-year-old",
  "distillery": "Midleton",
  "imageUrl":
"http://static.whiskybase.com/storage/whiskies/5/3/084/86415-big.jpg"
}

Write down the generated id (e.g. 1).

  1. Create a Rating entry using the explorer, use the id generated in the previous step as {id} parameter in the route:
POST /Whiskeys/{id}/reviews
{
  "rating": 5,
  "comment": "Had to travel across half of Ireland to find a place where they
serve this one"
}
  1. List all whiskeys including the reviews:
GET /Whiskeys
filter: {"include":["reviews"]}

Import sample data

At the moment, our application is storing all data in memory and they are lost on restart.

Let's write a short script that will populate the database with seed data on start.

  • Create a file server/boot/sample-data.js with the content as provided in the gist below.

  • Install async module

$ npm install async

Restart the application and list all whiskeys including the reviews again. You should see the sample records provided by the script we just wrote.

Add an AngularJS frontend

  1. Remove the client directory scaffolded by the LoopBack generator.

  2. Clone the client app from github into client:

$ git clone https://github.com/bajtos/nodeconf-workshop-client.git client
  1. Remove server/boot/root.js that was serving the / URL.

  2. Modify the server to serve the client app. Open server/server.js and uncomment the following two lines:

var path = require('path');
app.use(loopback.static(path.resolve(__dirname, '../client')));
  1. Restart the app and open it in browser. Check that the scaffolded client is served at the root URL:
$ open http://localhost:3000/

NOTE You will see an error message in server's console mentioning that the file lb-services.js was not found. This is expected, you will create this file in the next step.

Generate Angular client services

  1. Install the code generator:
$ npm install -g loopback-sdk-angular-cli
  1. Generate the services

    $ lb-ng server/server.js client/scripts/lb-services.js
    
  2. Update the index.html file to pick up the new resource. Add the following line to the list of scripts at the bottom:

<script src="scripts/lb-services.js"></script>
  1. View the API documentation for the client services
$ lb-ng-doc client/scripts/lb-services.js

Note: Reload the doc page if it does not contain any services. This is a know bug in docular.

Docular screenshot

Wire up the controllers and the REST API

  1. Modify the main controller in client/scripts/controllers/main.js to the list of all Whiskey objects using the REST API:
$scope.whiskeys = Whiskey.find();

Check out out the result in the browser.

  1. Modify client/scripts/controllers/details.js to fetch the currently viewed Whiskey object and include reviews in the response.

Hint: Whiskey.get does not support include parameter, use the method Whiskey.findOne instead.

Consult the documentation and the API docs generated by lb-ng-doc for the information on the API usage.

  1. Modify client/scripts/controllers/review.js to submit a new review and redirect back to the details page afterwards.

Celebrate!

Congratulations, you have finished the workshop. Now it's the time to have a pint of beer or cider; and perhaps show your friends what you have build today.

What to do next

// server/boot/sample-data.js
var async = require('async');
var WHISKEYS = [
{
"name": "Green Spot 12-year-old",
"distillery": "Midleton",
"imageUrl": "http://static.whiskybase.com/storage/whiskies/5/3/084/86415-big.jpg",
"reviews": [
{
"rating": 5,
"comment": "Had to travel across half of Ireland to find a place where they serve this one"
},
{
"rating": 3
},
{
"rating": 4
}
]
},
{
"name": "Jameson 12-year-old",
"distillery": "Jameson",
"imageUrl": "http://static.whiskybase.com/storage/whiskies/1/3/705/64900-big.jpg",
"reviews": [
{
"rating": 3
},
{
"rating": 2
},
{
"rating": 1,
"comment": "Too dull, this whiskey has no character at all."
}
]
},
{
"name": "Kilbeggan Distillery Reserve Malt Whiskey",
"distillery": "Kilbeggan Distillery",
"imageUrl": "http://static.whiskybase.com/storage/whiskies/5/1/970/74914-big.jpg"
}
];
module.exports = function(server) {
console.log('Importing sample data...');
var Whiskey = server.models.Whiskey;
async.each(WHISKEYS, function(whiskey, next) {
Whiskey.create(whiskey, function(err, created) {
if (err) return next(err);
async.each(whiskey.reviews || [], function(rating, cb) {
created.reviews.create(rating, cb);
},
next);
});
}, function(err) {
if (err)
console.error('Cannot import sample data.', err);
else
console.log('Sample data was imported.');
});
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment