I am extending the standard Ember example project (in coffeescript) to talk to an express/mongoose RESTful server. I have successfully fetched all and single records via POSTS using the recommended:
Find GET /people/123
Find All GET /people
Now when attempting to update a record via ember-data PUT, triggered through the Ember adapter.
Update PUT /people/123
And it's not working.
I am profiling the client side with Chrome dev tools, and server side with console.log. Here is what i'm seeing on the client side. Ember-data makes a PUT and an OPTIONS call to the server.
In the PUT I'm seeing that the payload contains the user edits, and the format looks correct.
The response tab shows a bunch of nonsense, so I'm assuming the problem is on the server side.
On the server side, when I dump the request variable I get this. The body is received as {post: {}}
, i.e. correct structure but empty of content. And the correct mongo id is received.
Here is a stacktrace from the server router:
Trace
at /home/vagrant/restl/node_modules/restgen/lib/routes.js:68:15
at callbacks (/home/vagrant/restl/node_modules/express/lib/router/index.js:161:37)
at param (/home/vagrant/restl/node_modules/express/lib/router/index.js:135:11)
at param (/home/vagrant/restl/node_modules/express/lib/router/index.js:132:11)
at param (/home/vagrant/restl/node_modules/express/lib/router/index.js:132:11)
at pass (/home/vagrant/restl/node_modules/express/lib/router/index.js:142:5)
at Router._dispatch (/home/vagrant/restl/node_modules/express/lib/router/index.js:170:5)
at Object.router (/home/vagrant/restl/node_modules/express/lib/router/index.js:33:10)
at next (/home/vagrant/restl/node_modules/express/node_modules/connect/lib/proto.js:190:15)
at Object.session [as handle] (/home/vagrant/restl/node_modules/express/node_modules/connect/lib/middleware/session.js:301:7)
at next (/home/vagrant/restl/node_modules/express/node_modules/connect/lib/proto.js:190:15)
at Object.cookieParser [as handle] (/home/vagrant/restl/node_modules/express/node_modules/connect/lib/middleware/cookieParser.js:60:5)
at next (/home/vagrant/restl/node_modules/express/node_modules/connect/lib/proto.js:190:15)
at Object.allowCrossDomain [as handle] (/home/vagrant/restl/app.js:21:5)
at next (/home/vagrant/restl/node_modules/express/node_modules/connect/lib/proto.js:190:15)
at Object.methodOverride [as handle] (/home/vagrant/restl/node_modules/express/node_modules/connect/lib/middleware/methodOverride.js:49:5)
at next (/home/vagrant/restl/node_modules/express/node_modules/connect/lib/proto.js:190:15)
at multipart (/home/vagrant/restl/node_modules/express/node_modules/connect/lib/middleware/multipart.js:60:27)
at /home/vagrant/restl/node_modules/express/node_modules/connect/lib/middleware/bodyParser.js:57:9
at urlencoded (/home/vagrant/restl/node_modules/express/node_modules/connect/lib/middleware/urlencoded.js:48:27)
at /home/vagrant/restl/node_modules/express/node_modules/connect/lib/middleware/bodyParser.js:55:7
at IncomingMessage.<anonymous> (/home/vagrant/restl/node_modules/express/node_modules/connect/lib/middleware/json.js:82:9)
at IncomingMessage.EventEmitter.emit (events.js:92:17)
at _stream_readable.js:910:16
at process._tickCallback (node.js:415:13)
What's wrong with my express REST server?
Here are the relevant parts of the server code, which is forked from npm restgen.
var express = require('express')
, http = require('http')
, path = require('path')
, restgen = require('restgen');
var app = express()
, mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/rest');
// development only
if ('development' == app.get('env')) {
app.use(express.logger({ format: '\x1b[1m :date \x1b[1m:method\x1b[0m \x1b[33m:url\x1b[0m :response-time ms\x1b[0m :status' }));
}
//CORS middleware
var allowCrossDomain = function(req, res, next) {
res.header("Access-Control-Allow-Origin", "*");
res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,OPTIONS');
res.header('Access-Control-Allow-Headers', 'Content-Type');
next();
}
// all environments
app.configure(function() {
app.set('root', __dirname);
app.set('port', process.env.PORT || 3000); //3000
app.set('views', __dirname + '/views');
app.set('view engine', 'jade');
app.use(express.favicon());
app.use(express.bodyParser());
app.use(express.methodOverride());
app.use(allowCrossDomain);
app.use(express.cookieParser('your secret here'));
app.use(express.session());
app.use(app.router);
app.use(express.static(path.join(__dirname, 'public')));
app.use(restgen.ErrorHandler)
});
restgen.Initialize(app, mongoose);
app.use(function(req, res, next){
next(restgen.RestError.NotFound.insert(req.url));
});
// example of how to throw a 404
app.get('/404', function(req, res, next){
next(restgen.RestError.NotFound.insert(req.url));
});
// example of how to throw a 500
app.get('/500', function(req, res, next){
next(new Error('keyboard cat!'));
});
if(!module.parent) {
http.createServer(app).listen(app.get('port'), function(){
console.log('Express server listening on port ' + app.get('port'));
});
}
exports.app = app;
Somewhere on the client side the request payload is getting lost. I'm happy to post additional parts of the server side code, such as routes, if it's relevant. Please ask in comments.
Here is the Ember side, which I'm pretty sure is correct.
# ===== Adapter =====
# Extended to handle mongo's _id as primarykey
App.Adapter = DS.RESTAdapter.extend(
serializer: DS.RESTSerializer.extend(
primaryKey: (type) -> "_id"
)
)
DS.RESTAdapter.reopen({url: 'http://localhost:3000'});
# ===== Store =====
App.Store = DS.Store.extend(
revision: 12
adapter: App.Adapter
)
# ===== Controller =====
module.exports = App.PostController = Ember.ObjectController.extend
save: ->
@get("store").commit()
@get("target.router").transitionTo "posts.index"
# ==== Model ====
module.exports = App.Post = DS.Model.extend
title: DS.attr 'string'
author: DS.attr 'string'
intro: DS.attr 'string'
extended: DS.attr 'string'
publishedAt: DS.attr 'date'
Version information from npm ls
vagrant@precise32:~/brunch-ember$ npm ls
brunch-with-ember-reloaded@0.0.3 /home/vagrant/brunch-ember
├─┬ auto-reload-brunch@1.5.2
│ └─┬ ws@0.4.20
│ ├── commander@0.6.1
│ ├── options@0.0.5
│ └── tinycolor@0.0.1
├─┬ chai@1.7.2
│ └── assertion-error@1.0.0
├─┬ clean-css-brunch@1.5.1
│ └─┬ clean-css@0.10.2
│ └─┬ commander@1.1.1
│ └── keypress@0.1.0
├─┬ coffee-script-brunch@1.5.1
│ └── coffee-script@1.6.3
├── css-brunch@1.5.1
├─┬ dc@1.4.0 extraneous
│ ├── crossfilter@1.2.0
│ └─┬ d3@3.2.6
│ └─┬ jsdom@0.5.7
│ ├─┬ contextify@0.1.6
│ │ └── bindings@1.1.1
│ ├── cssom@0.2.5
│ ├── cssstyle@0.2.3
│ ├── htmlparser@1.7.6
│ ├── nwmatcher@1.3.1
│ └─┬ request@2.22.0
│ ├── aws-sign@0.3.0
│ ├── cookie-jar@0.3.0
│ ├── forever-agent@0.5.0
│ ├─┬ form-data@0.0.8
│ │ ├── async@0.2.9
│ │ └─┬ combined-stream@0.0.4
│ │ └── delayed-stream@0.0.5
│ ├─┬ hawk@0.13.1
│ │ ├─┬ boom@0.4.2
│ │ │ └── hoek@0.9.1
│ │ ├── cryptiles@0.2.2
│ │ ├── hoek@0.8.5
│ │ └─┬ sntp@0.2.4
│ │ └── hoek@0.9.1
│ ├─┬ http-signature@0.10.0
│ │ ├── asn1@0.1.11
│ │ ├── assert-plus@0.1.2
│ │ └── ctype@0.5.2
│ ├── json-stringify-safe@4.0.0
│ ├── mime@1.2.9
│ ├── node-uuid@1.4.0
│ ├── oauth-sign@0.3.0
│ ├── qs@0.6.5
│ └── tunnel-agent@0.3.0
├─┬ ember-handlebars-brunch@1.0.4 (git+ssh://git@github.com:bartsqueezy/ember-handlebars-brunch.git#19b9cfd141
│ └── coffee-script@1.6.2
├─┬ express@3.3.4
│ ├── buffer-crc32@0.2.1
│ ├─┬ commander@1.2.0
│ │ └── keypress@0.1.0
│ ├─┬ connect@2.8.4
│ │ ├── bytes@0.2.0
│ │ ├── formidable@1.0.14
│ │ ├── pause@0.0.1
│ │ ├── qs@0.6.5
│ │ └── uid2@0.0.2
│ ├── cookie@0.1.0
│ ├── cookie-signature@1.0.1
│ ├── debug@0.7.2
│ ├── fresh@0.1.0
│ ├── methods@0.0.1
│ ├── mkdirp@0.3.5
│ ├── range-parser@0.0.4
│ └─┬ send@0.1.3
│ └── mime@1.2.9
├─┬ jade@0.32.0
│ ├── character-parser@1.0.2
│ ├─┬ commander@1.2.0
│ │ └── keypress@0.1.0
│ ├─┬ constantinople@1.0.1
│ │ └─┬ uglify-js@2.3.6
│ │ ├── async@0.2.9
│ │ ├─┬ optimist@0.3.7
│ │ │ └── wordwrap@0.0.2
│ │ └─┬ source-map@0.1.25
│ │ └── amdefine@0.0.5
│ ├── mkdirp@0.3.5
│ ├─┬ monocle@0.1.48
│ │ └─┬ readdirp@0.2.5
│ │ └─┬ minimatch@0.2.12
│ │ ├── lru-cache@2.3.0
│ │ └── sigmund@1.0.0
│ ├─┬ transformers@2.0.1
│ │ ├─┬ css@1.0.8
│ │ │ ├── css-parse@1.0.4
│ │ │ └── css-stringify@1.0.5
│ │ ├─┬ promise@2.0.0
│ │ │ └── is-promise@1.0.0
│ │ └─┬ uglify-js@2.2.5
│ │ ├─┬ optimist@0.3.7
│ │ │ └── wordwrap@0.0.2
│ │ └─┬ source-map@0.1.25
│ │ └── amdefine@0.0.5
│ └─┬ with@1.1.0
│ └─┬ uglify-js@2.3.6
│ ├── async@0.2.9
│ ├─┬ optimist@0.3.7
│ │ └── wordwrap@0.0.2
│ └─┬ source-map@0.1.25
│ └── amdefine@0.0.5
├── javascript-brunch@1.5.1
├─┬ karma@0.8.1
│ ├── chokidar@0.5.3
│ ├── coffee-script@1.4.0
│ ├── colors@0.6.0-1
│ ├── dateformat@1.0.2-1.2.3
│ ├─┬ glob@3.1.20
│ │ ├── graceful-fs@1.2.2
│ │ └── inherits@1.0.0
│ ├── growly@1.1.1
│ ├─┬ http-proxy@0.10.0
│ │ ├── pkginfo@0.2.3
│ │ └─┬ utile@0.1.7
│ │ ├── async@0.1.22
│ │ ├── deep-equal@0.0.0
│ │ ├── i@0.3.1
│ │ ├── mkdirp@0.3.5
│ │ ├── ncp@0.2.7
│ │ └── rimraf@1.0.9
│ ├─┬ istanbul@0.1.22
│ │ ├── abbrev@1.0.4
│ │ ├── async@0.1.22
│ │ ├─┬ escodegen@0.0.23
│ │ │ ├── esprima@1.0.3
│ │ │ ├── estraverse@0.0.4
│ │ │ └─┬ source-map@0.1.25
│ │ │ └── amdefine@0.0.5
│ │ ├── esprima@0.9.9
│ │ ├── fileset@0.1.5
│ │ ├─┬ handlebars@1.0.12
│ │ │ └─┬ uglify-js@2.3.6
│ │ │ ├── async@0.2.9
│ │ │ └─┬ source-map@0.1.25
│ │ │ └── amdefine@0.0.5
│ │ ├── mkdirp@0.3.5
│ │ ├── nopt@2.0.0
│ │ ├── which@1.0.5
│ │ └── wordwrap@0.0.2
│ ├─┬ LiveScript@1.0.1
│ │ └── prelude-ls@1.0.0
│ ├── lodash@0.9.2 invalid
│ ├─┬ log4js@0.5.6
│ │ └── async@0.1.15
│ ├── mime@1.2.7
│ ├─┬ minimatch@0.2.9
│ │ ├── lru-cache@2.0.4
│ │ └── sigmund@1.0.0
│ ├─┬ optimist@0.3.5
│ │ └── wordwrap@0.0.2
│ ├── pause@0.0.1
│ ├── q@0.8.12
│ ├─┬ rimraf@2.1.4
│ │ └── graceful-fs@1.2.2
│ ├─┬ socket.io@0.9.13
│ │ ├── base64id@0.1.0
│ │ ├── policyfile@0.0.4
│ │ ├── redis@0.7.3
│ │ └─┬ socket.io-client@0.9.11
│ │ ├─┬ active-x-obfuscator@0.0.1
│ │ │ └── zeparser@0.0.5
│ │ ├── uglify-js@1.2.5
│ │ ├─┬ ws@0.4.27
│ │ │ ├── commander@0.6.1
│ │ │ ├── options@0.0.5
│ │ │ └── tinycolor@0.0.1
│ │ └── xmlhttprequest@1.4.2
│ └── xmlbuilder@0.4.2
├─┬ mocha@1.12.0
│ ├── commander@0.6.1
│ ├── debug@0.7.2
│ ├── diff@1.0.2
│ ├─┬ glob@3.2.1
│ │ ├── graceful-fs@1.2.2
│ │ ├── inherits@1.0.0
│ │ └─┬ minimatch@0.2.12
│ │ ├── lru-cache@2.3.0
│ │ └── sigmund@1.0.0
│ ├── growl@1.7.0
│ ├─┬ jade@0.26.3
│ │ └── mkdirp@0.3.0
│ ├── mkdirp@0.3.5
│ └── ms@0.3.0
├── moment@2.0.0
├─┬ mongoose@3.6.14
│ ├── hooks@0.2.1
│ ├─┬ mongodb@1.3.11
│ │ ├── bson@0.1.9
│ │ └── kerberos@0.0.3
│ ├── mpath@0.1.1
│ ├─┬ mpromise@0.2.1
│ │ └── sliced@0.0.4
│ ├── ms@0.1.0
│ ├── muri@0.3.1
│ ├── regexp-clone@0.0.1
│ └── sliced@0.0.3
├─┬ restgen@1.0.2
│ ├── UNMET DEPENDENCY cli-color 0.2.2
│ ├── UNMET DEPENDENCY commander https://github.com/alexferreira/commander.js/tarball/master
│ ├── UNMET DEPENDENCY ejs latest
│ ├── UNMET DEPENDENCY fleck 0.5.1
│ ├── UNMET DEPENDENCY fs-extra 0.6.1
│ └── UNMET DEPENDENCY rsvp-that-works 1.2.0
├── showdown@0.3.1
├─┬ stylus-brunch@1.5.1
│ ├─┬ nib@0.9.2
│ │ └─┬ stylus@0.31.0
│ │ ├── cssom@0.2.5
│ │ ├── debug@0.7.2
│ │ └── mkdirp@0.3.5
│ ├─┬ node-sprite@0.1.1
│ │ ├── coffee-script@1.3.3
│ │ ├── imagemagick@0.1.2
│ │ ├─┬ seq@0.3.5
│ │ │ ├─┬ chainsaw@0.0.9
│ │ │ │ └── traverse@0.3.9
│ │ │ └─┬ hashish@0.0.4
│ │ │ └── traverse@0.6.3
│ │ ├── underscore@1.3.1
│ │ └── watch@0.5.1
│ └─┬ stylus@0.32.1
│ ├── cssom@0.2.5
│ ├── debug@0.7.2
│ └── mkdirp@0.3.5
├─┬ supertest@0.6.0
│ ├── methods@0.0.1
│ └─┬ superagent@0.10.0
│ ├─┬ better-assert@0.1.0
│ │ └── callsite@1.0.0
│ ├── cookiejar@1.3.0
│ ├── emitter-component@0.0.6
│ ├── formidable@1.0.9
│ ├── mime@1.2.5
│ └── qs@0.5.2
├── twitter-bootstrap@2.1.1
└─┬ uglify-js-brunch@1.5.1
└─┬ uglify-js@2.2.5
├─┬ optimist@0.3.7
│ └── wordwrap@0.0.2
└─┬ source-map@0.1.25
└── amdefine@0.0.5
I haven't dug in too far, but I think this might be a subtle difference between ember-data and restgen: restgen expects form data (title=foo&body=bar) unless the url ends in ".json". But ember data is uploading json without adding that to the URL.
I haven't yet found any docs from either side indicating how to change the default behavior, but if that is indeed the issue then you should be able to figure out a workaround.
Update: a quick way to check this is to right-click the ajax request in Chrome's Developer Tools, and choose "Copy as cURL", then paste that into a notepad and edit the url to include ".ajax" on the end, then run the command from the command line. If your server processes this correctly, then that's a good sign that we're on the right track.