Express REST server losing payload from Ember-data PUT

connect, ember-data, express, mongoose, node.js

Solution

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.

Problem

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. Profiling: client 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. Profiling: server 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? Server side code 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. Client side code 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' ``` Update #1 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 ```

Original source