because, just because
the most basic express application
- Install express on the server's package.json
npm install express
- Require express
var express = require('express');
- Create an express "app"
var app = express();
- Use the app to create a route to the root ('/')
app.get('/', function(req, res) {
res.send('okay, yeah');
});
Note: res.send(something) is just an express wrapper for res.end(something), so it's doing a write(something) followed by an end(). It also does some encoding and status code checks.
together
- Take the array from the
ListControllerin the angular app
var people = [ ... ];
- Send the json
app.send(people);- `app.send(JSON.stringify(people));``
- Use
app.json()to "send" the array
app.json(people);send()is actually smart enough to know when you're sending json, but it's nice to be explicit
- Check it in a browser
together
- Use an anonymous function before any routes
app.use(function(req, res, next){ ... })- this is a pipeline for the request and response streams
- the function will require a
next();function to be called if you want anything to happen after it - if you place it after any routes, it will never run
- Console log the
date, themethodand theurl
console.log((new Date()).toString() + " " + req.method + " " + req.url)
Note: This is not a great logging solution, it's not handling errors or status codes. For that you'll want a real express middleware logging solution. It's just logging is such a nice and easy thing to show you some easy middleware.
together
- Create a route for a listing
app.get('/people', function(req, res){ ... });- the root route is fine for us right now, but it's not RESTful
- RESTful does have a few rules, like having a single "resource" for making queries against
together
- Look at the angular app
- Run the angular app
npm installgulp
together
- Add ng-resource to the project
bower install angular-resource
- Include it as a dependency on the app (in app.js)
angular.module('resthitter', ['ui.bootstrap', 'xeditable', 'ngRoute', 'ngResource']).config({ ... });
- Create a
Peopleservice
- create a new file under the services directory,
People.js - these are normally created as factories
angular.module('resthitter').factory('People', ['$resource', function($resource){ ... }]);
- Add the
$resourcedependency
$resourceis a wrapper for$http- it is meant to allow you to easily set up hitting restful APIs
- it has 4 main functions:
query(),get(),save(), anddelete()(andremove()) - these map to the primary RESTful methods you'll be using (
GETall,GETa single,POSTa new entry or change, andDELETEa single - $resource docs
- Return a
$resourceobject
return $resource('http://localhost:7000/people/:id');- Note: the full url that can be parsed internally by
$resourceto give it the right urls for each RESTful action - Also note:
$resourceis as simple as it can be out of the box, but you can extend it to whatever you need, (for example, if you need it to make aPUTrequest against an API)
together
- Inject
PeopleintoListController - Use
People.query();
var people = People.query();
- Check your network activity in the browser and you will see it made a request
- Make the edits to have that data display in the template's
ng-repeat
together
- Create a new middleware option
app.use(function(req, res, next){ ... });
- Add the following two headers:
res.header("Access-Control-Allow-Origin", "*");res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
- Call
next();to pass it through to the next middleware
next();
app.use(function(req, res, next) {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
next();
});
Note: Cross Origin Resource Sharing is the most accepted way to subvert the Single Origin Policy, which is a security policy to prevent malicious actions. It's up to the server, and it's generally accepted that you're not going to be allowing * unless you're a truly public API -- usually a read-only API will do that. Usually people will at least limit the sharing to *.domain.com.
together
- Get the value of the current page's id from the
$routeParams
- inject
$routeParams - current
idis$routeParams.id- it's getting this from the
$routeProviderconfig in app.js
- it's getting this from the
- Set the value of the person variable to the
$resource.get()for the current id
var person = People.get({id: $routeParams.id});- that object parameter is the parameter set in the $resource definition in the service
- so it's equivalent to sending a
GETrequest to:http://localhost:7000/people/1
- Make sure the appropriate value gets displayed on the template
together
- Create a new route, similar to the format of our angular
$routeParamsand$resource
app.get('/people/:id', function(req, res){ ... });- the id is now available on
req.params.id, courtesy of express
- If we find that
req.params.idin the people array, return the corresponding person object
forEachor some other loopish structurepeople.forEach(function(person){ ... });for(var index = 0; i < people.length; i++){ ... }
- an
ifchecking the id of each array elementif(person.id == req.params.id){ ... }
- return the matching element
return person;
- If we don't find it, send a
404
res.status(404).end();
app.get('/people/:id', function(req, res){
people.forEach(function(person){
if(person.id == req.params.id){
res.json(person);
}
});
res.status(404).end();
});
Note: forEach, (as well as any vanilla loop) is going to be a synchronous process. This means it will be blocking. This is partly why people store data in noSQL solutions when working with Node, so lookups can be offloaded to Mongo or Redis or whatever with an async call. But we can also "simulate asynchronisity" by simply wrapping the interior with a setTimeout()... But then the 404 will get called before it has a chance to check them, so you'll have to handle that somehow. You could use a lib for handling async functions or even a promise pattern -- or just use a counter. But these are the problems running Node has.
var counter = 0;
people.forEach(function(person){
setTimeout(function(){
if(person.id == req.params.id){
res.json(person);
}
counter++;
if(counter == people.length){
// then we've gone through them all and it wasn't found
res.status(404).end();
}
}, 10);
});
together
- Since we have included xeditable, we have some directives available to us
- add the attribute
editable-textto the name and email texts - use them the same way you would use
ng-model <h1 editable-text="dc.person.name">{{ dc.person.name }}</h1>- Hint: wrap the editable field in a
<span>to avoid the line across the page
- Create a save button
- use
ng-clickto have that button call a save function in your controller
- You can call a save directly from the existing
$resourceyou got from the.get()in that controller
dc.person.$save();- Note: this returns a promise, so we can chain it with a
.then(success, failure); - use this
successfunction to console log something to let us know if we're succeeding!
dc.person.$save().then(function(){
console.log("success!");
});
Note: this can also be done with a call to the $resource factory
Name.save({}, dc.person, function(){ console.log("success!"); });- where the first param is
$resourceparams - the second param is the
POST's body- in this case, we're posting the json for the
person
- in this case, we're posting the json for the
- the third param is a success callback
together
- Set up an
app.postfor the/peopleroute
app.post('/people', function(req, res){ ... });
- Install
body-parser
npm install body-parser- this is express middleware
- we need this to get the
POSTdata
- Require the body-parser
var bodyParser = require('body-parser');
- Use the body-parser to parse for json
app.use(bodyParser.json());- now in our routes,
req.bodyhas been populated with parsed json, assuming that's what was posted
- Get the
personobject fromreq.body
- var person = req.body;
on your own
- Loop through the array in some way (
for()or.forEach()or whatever) - Find the match on
id - Set that index to the post data we just got
- Don't forget to end the response!
res.end();
together
- Inject the
$locationservice toDetailController
.controller('DetailController', ['People', '$location', function(People, $location){ ... }]);$locationis an angular wrapper forwindow.location
- Use the
.then()promise chained success function to change the location
$location.path('/');
together
- Add a button to the list view
- actually, a link will be more to the point, but we can make it look like a button
<a class="btn btn-info" href="/#/new">new</a>
- Create the new route -- just have it go to the detail template
.when('/new', { ... })- just populate the route the same as the edit route
- In the Controller, check for an undefined
$routeParam.id
if($routeParam.id === undefined){ ... }
- If it's not defined, create a new
Peopleobject and populate it with some default values
var dc.person = new Person();dc.person.name = "placeholder";dc.person.twitter = "@placeholder";- if it is defined, just do what we were doing before
- Try it
together
- If it gets through the
forEach, instead of throwing a404, check if the id is undefined
if(postedPerson.id === undefined){ ... }
- Give the posted person an appropriate id
postedPerson.id = people.length + 1;
- Add the
postedPersonto thepeoplearray
people.push(postedPerson);
- What are the obvious flaws with this?
- hint: if we added a delete...
together
- Add a delete button to the list template with an
ng-click
<button class="btn" ng-click="lc.delete(person.id)">delete</button>
- Create the
delete(id)function
lc.delete = function(id){ ... };
- Send a
DELETErequest to the server
People.delete({id: id});- remember, this is like sending a
DELETErequest tohttp://localhost:7000/people/:id
- Try it
- Alter CORS to allow
DELETE
res.header('Access-Control-Allow-Methods', 'POST, GET, PUT, DELETE, OPTIONS');
- Create a delete route on the server
app.delete('/people/:id', function(req, res){ ... })
- Find and delete the entry
people.splice(index);- don't forget to
res.end()
- Return a
404if you didn't find it
res.status(404).end();
- In the controller, after a delete, refresh the data
People.delete({id: id}, function(){ ... });lc.people = People.query();- Note: in a real scenario, you probably wouldn't want to be hammering the server -- if your
DELETEcame back with a success, you should remove it from the existinglc.peoplearray- thankfully, this isn't real. Go home.