0
votes

I am building a web app that uses Ember for the client side and Node for the server side. The database I am using is Mongo. Everything is working on the server side (I am able to use Postman to GET, POST, PUT, and DELETE users. I think I almost have everything hooked up on the client side, but Ember is throwing me one final error when I navigate to the /users route:

ember.debug.js:28535 Error while processing route: users Assertion Failed: You must include an 'id' for user in an object passed to 'push' Error: Assertion Failed: You must include an 'id' for user in an object passed to 'push'

Any ideas why this is happening/ how to fix it?

Here are the relevant parts of my server.js file:

var User     = require('./models/user');

// configure app to use bodyParser()
// this will let us get the data from a POST
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());

// Add CORS headers
app.use(function (request, response, next) {
    response.header("Access-Control-Allow-Origin", "http://localhost:4200");
    response.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
    response.header("Access-Control-Allow-Resource", "*");
    response.header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE");
    next();
});

var port = process.env.PORT || 8080;        // set our port

// ROUTES FOR OUR API

var router = express.Router();              // get an instance of the express Router

// test route to make sure everything is working (accessed at GET http://localhost:8080/api)
router.get('/', function(request, response) {
    response.json({ message: 'hooray! welcome to our api!' });   
});

// more routes for our API will happen here

// REGISTER OUR ROUTES
// all of our routes will be prefixed with /api/v1
app.use('/api/v1/', router);

// START THE SERVER
app.listen(port);
console.log('Magic happens on port ' + port);


// more routes for our API will happen here

// on routes that end in /users
// ----------------------------------------------------
router.route('/users')

    // create a user (accessed at POST http://localhost:8080/api/v1/users)
    .post(function(request, response) {

        var user = new User();      // create a new instance of the User model
        user.name = request.body.name;  // set the user's name (comes from the request)
        user.email = request.body.email; // set the users's email property 
        user.password = request.body.password; //set the user's password property

        // save the user and check for errors
        user.save(function(error) {
            if (error)
                response.send(error);

            response.json({ message: 'User created!' });
        });
    })

    // get all the users (accessed at GET http://localhost:8080/api/v1/users)
    .get(function (request, response) {
        User.find(function (error, users) {
            if (error) response.send(error);
            response.json(users);
        });
    });

// on routes that end in /users/:user_id
// ----------------------------------------------------
router.route('/users/:user_id')

    // get the user with that id (accessed at GET http://localhost:8080/api/users/:user_id)
    .get(function (request, response) {
        User.findById(request.params.user_id, function (error, user) {
            if (error) response.send(error);
            response.json(user);
        });
    })

    // update the user with this id (accessed at PUT http://localhost:8080/api/users/:user_id)
    .put(function (request, response) {

        // use our user model to find the user we want
        User.findById(request.params.user_id, function(error, user) {
            if (error) response.send(error);

            // update the user info
            user.name = request.body.name;
            user.email = request.body.email;
            user.password = request.body.password;

            // save the user
            user.save(function(error) {
                if (error) response.send(error);
                response.json({ message: 'User updated!' });
            });
        });
    })

    // delete the user with this id (accessed at DELETE http://localhost:8080/api/users/:user_id)
    .delete(function (request, response) {
        User.remove({
            _id: request.params.user_id
        }, function(error, user) {
            if (error) res.send(err);

            response.json({ message: 'Successfully deleted' });
        });
    });

Here is my application adapter (on the Ember side):

import DS from 'ember-data';

export default DS.RESTAdapter.extend({
    host: 'http://localhost:8080',
    namespace: 'api/v1'
});

Here is my serializer (on the Ember side):

import JSONAPISerializer from 'ember-data/serializers/json-api';
import DS from 'ember-data';

export default JSONAPISerializer.extend({
    primaryKey: '_id'
});

export default DS.JSONSerializer;

Here is my model (on the Ember side):

import Model from 'ember-data/model';
import attr from 'ember-data/attr';

export default Model.extend({
  name: attr('string'),
  email: attr('string'),
  password: attr('string')
});

Here is my users.js route (on the Ember side):

import Ember from 'ember';

export default Ember.Route.extend({
    model() {
        return this.store.findAll('user');
    },
    actions: {
        createUser(newName, newEmail, newPassword) {
           this.store.createRecord('user', {
               name: newName,
               email: newEmail,
               password: newPassword
           }).save();
        },
        updateUser(user) {
            user.save();
        },
        deleteUser(user) {
            user.destroyRecord();
        }
    }
});
1
Is it intentional to use a DS.RESTAdapter instead of a DS.JSONAPIAdapter while using a JSONAPISerializer ?bmeurant
Also, I don't understand the export default DS.JSONSerializerbmeurant

1 Answers

0
votes

From your posted code your Application adapter is defined as RESTAdapter and your serializer is defined as JSONAPISerializer. Ember will expect JSON API format as defined on jsonapi ORG site

To solve you can do two things. Make node output JSON api format with data id and attributes format using this library for example: https://github.com/SeyZ/jsonapi-serializer or just make sure that you use REST serializer and adapter for your resources.

Try to define your application serializer like this

import DS from 'ember-data';
export default DS.RESTSerializer.extend({
  primaryKey: '_id',
  serializeId: function(id) {
    return id.toString();
  }
});

Make sure that JSON output include something like this:

{   
  users: [
      {"_id": "13ac3f0d4e29d794d9f62898","name":"", "email": "" },
      {"_id": "13ac3f0d4e29d794d9f62899","name":"", "email": "" }
  ]
}

Another small issue is that your API endpoint on server side that creates, deletes or updates the user returns message instead of the users resource

user.save(function(error) {
            if (error) response.send(error);
            response.json({ message: 'User updated!' });
        });

You should always return the updated resource, in this case you need to return the user as you do in GET /users/ID route with proper status and error code, for example:

When creating a user ember would expect that you return 201 CREATED status code and json response of user resource. For DELETE and PATCH/PUT (Update) you need to return status code 200. In all cases you need to use response.json(user) instead of outputting messages.

If you are not able to process the request (for example when creating a user email is already taken) you should respond with 422 and return proper error message. See this for handling errors: http://emberjs.com/api/data/classes/DS.Errors.html

Hope it helps.