0

I'm converting an old LAMP stack project to use Node/Express/MongoDB instead of PHP/Laravel/MySQL. I built out my routes the same way I did in Laravel and have tested the routes I've built using Postman. Every route works as it should through testing in Postman.

However, when I try to use these routes in the display.js file that I wrote, the only route that works is GET /players. I am looking for some insight on these routes and the display file I'm using.

When I click the edit button, my GET /player/:id route grabs the correct information (status code 200 and preview shows JSON object) but will not populate the fields in my form.

When I click the delete button, I get a 404 status code with a response of "Cannot GET /players/123/delete". How do I adjust my display code to use DELETE instead of GET? (I believe I can refactor my all of my routes to just /players/:id using the correct http request method if I can figure out how to pass the request method in the display.js code).

When I try to submit a new record, I get a 400 status code with validation error saying Path age is required. It's actually the same error for each field name, so I assume that I'm not passing the values from the field into the JSON object correctly.

I haven't been able to test patch, but believe it should be remedied through fixing DELETE /players/:id and GET /players/:id.

Any help is appreciated. Code is below for display and server js files.

display.js

// this is the base url to which all your requests will be made
var baseURL = window.location.origin;
$(document).ready(function(){
// $.ajaxSetup({
//     headers: {
//         'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
//     }
// });
$('#table').click(function(event) { // generates the table
    // change the url parameters based on your API here
    // Using an JQuery AJAX GET request to get data form the server
    $.getJSON(baseURL+'/players', function(data) {
        generateTable(data, $('#container'));
    });
});

$('#form').click(function(event) {
    // creating an empty form
    generateForm(null, $('#container'));
});
// Handle table click event for delete
$('#container').on('click', '.delete', function(event) {
    var id = $(this).val();
    // change the url parameters based on your API here
    // remember to create delete functionality on the server side (Model and Controller)
    // Using an JQuery AJAX GET request to get data form the server
    $.getJSON(baseURL+"/players/"+id+"/delete", function(data) {
        //Generate table again after delete
        //change the url based on your API parameters here
        // Using an JQuery AJAX GET request to get data from the server
        $.getJSON(baseURL+'/players', function(data) {
            generateTable(data, $('#container'));
        });
    });
});

// Handle form submit event for both update & create
// if the ID_FIELD is present the server would update the database otherwise the server would create a record in the database
$('#container').on('submit', '#my-form', function(event) {
    var id = $('#id').val();
    console.log(id);
    if (id != "") {
        event.preventDefault();
        submitForm(baseURL+"/players/"+id+"/edit", $(this));
    } else {
        event.preventDefault();
        submitForm(baseURL+"/player", $(this));
    }
});

// Handle table click event for edit
// generates form with prefilled values
$('#container').on('click', '.edit', function(event) {
    // getting id to make the AJAX request
    var id = $(this).val();
    // change the url parameters based on your API here
    // Using an JQuery AJAX GET request to get data form the server
    $.getJSON(baseURL+'/players/'+id, function(data) {
        generateForm(data, $('#container'));
    });
});

// function to generate table
function generateTable(data, target) {
    clearContainer(target);
    //Change the table according to your data
    var tableHtml = '<table><thead><tr><th>Name</th><th>Age</th><th>Position</th><th>Team</th><th>Delete</th><th>Edit</th></tr></thead>';
    $.each(data, function(index, val) {
        tableHtml += '<tr><td>'+val.playername+'</td><td>'+val.age+'</td><td>'+val.position+'</td><td>'+val.team+'</td><td><button class="delete" value="'+val._id+'">Delete</button></td><td><button class="edit" value="'+val._id+'">Edit</button></td></tr>';
    });
    tableHtml += '</table>';
    $(target).append(tableHtml);
}

// function to generate form
function generateForm(data, target){
    clearContainer(target);
    //Change form according to your fields
    $(target).append('<form id="my-form"></form>');
    var innerForm = '<fieldset><legend>Player Form</legend><p><label>Player Name: </label>'+'<input type="hidden" name="id" id="id"/>'+'<input type="text" name="playername" id="playername" /></p>' + '<p><label>Age: </label><input type="text" name="age" id="age" /></p>'+ '<p><label>Hometown: </label><input type="text" name="city" id="city" />'+ ' ' + '<input type="text" name="country" id="country" /></p>' + '<p><label>Gender: </label><input type="text" name="gender" id="gender" /></p>'+ '<p><label>Handedness: </label><input type="text" name="handedness" id="handedness" /></p>'+ '<p><label>Broom: </label><input type="text" name="broom" id="broom" /></p>'+ '<p><label>Position: </label><input type="text" name="position" id="position" /></p>'+ '<p><label>Team: </label><input type="text" name="team" id="team" /></p>'+ '<p><label>Favorite Color: </label><input type="text" name="favoritecolor" id="favoritecolor" /></p>'+ '<p><label>Headshot: </label><input type="text" name="headshot" id="Headshot" /></p>'+ '<input type="submit"/>';
    $('#my-form').append(innerForm);
    //Change values according to your data
    if(data != null){
        $.each(data, function(index, val) {
            $('#id').val(val._id);
            $('#playername').val(val.playername);
            $('#age').val(val.age);
            $('#city').val(val.city);
            $('#country').val(val.country);
            $('#gender').val(val.gender);
            $('#handedness').val(val.handedness);
            $('#broom').val(val.broom);
            $('#position').val(val.position);
            $('#team').val(val.team);
            $('#favoritecolor').val(val.favoritecolor);
            $('#Headshot').val(val.headshot);
        });
    }
}

function submitForm(url, form){
    $.post(url, form.serialize(), function(data) {
        showNotification(data, $('#notification'));
    });
}

function showNotification(data, target){
    clearContainer(target);
    target.append('<p>'+data+'</p>');
}

function clearContainer(container){
    container.html('');
}
});

server.js

const _ = require('lodash');
const {ObjectID} = require('mongodb');
const express = require('express');
const path = require('path');
const bodyParser = require('body-parser');

var {mongoose} = require('./db/mongoose');
var {Player} = require('./models/player');

var app = express();
const port = process.env.PORT || 3000;

app.use(express.static(__dirname+'./../public'));
app.use(bodyParser.json());

app.get('/', (req, res) => {
  res.sendFile('index.html');
});

app.post('/player', (req, res) => {
  var player = new Player({
    playername: req.body.playername,
    age: req.body.age,
    city: req.body.city,
    country: req.body.country,
    gender: req.body.gender,
    handedness: req.body.handedness,
    broom: req.body.broom,
    position: req.body.position,
    team: req.body.team,
    favoritecolor: req.body.favoritecolor,
    headshot: req.body.headshot
  });
  player.save().then((doc) => {
    res.send(doc);
  }, (e) => {
    res.status(400).send(e);
  });
});

app.get('/players', (req, res) => {
  Player.find().then((players) => {
    res.send(players);
  }, (e) => {
    res.status(400).send(e);
  });
});

app.get('/players/:id', (req, res) => {
  var id = req.params.id;
  if (!ObjectID.isValid(id)) {
    return res.status(404).send();
  }
  Player.findById(id).then((player) => {
    if (player) {
      res.send(player);
    } else {
      return res.status(404).send();
    }
  }).catch((e) => {
    res.status(400).send();
  });
});

app.delete('/players/:id/delete', (req, res) => {
  var id = req.params.id;
  if (!ObjectID.isValid(id)) {
    return res.status(404).send();
  }
  Player.findByIdAndRemove(id).then((player) => {
    if (player) {
      res.send(player);
    } else {
      return res.status(404).send();
    }
  }).catch((e) => {
    res.status(400).send();
  });
});

app.patch('/players/:id/edit', (req, res) => {
  var id = req.params.id;
  var body = _.pick(req.body, ['playername', 'age', 'city', 'country', 'gender', 'handedness', 'broom', 'position', 'team', 'favoritecolor', 'headshot']);
  if (!ObjectID.isValid(id)) {
    return res.status(404).send();
  }
  Player.findByIdAndUpdate(id, {$set: body}, {new: true}).then((player) => {
    if (!player) {
      return res.status(404).send();
    } else {
      res.send(player);
    }
  }).catch((e) => {
    res.status(400).send();
  })
});

app.listen(port, () => {
  console.log(`Started on port ${port}`);
});

module.exports = {app};
SepticReVo
  • 45
  • 9

2 Answers2

1

When I click the delete button, I get a 404 status code with a response of "Cannot GET /players/123/delete". How do I adjust my display code to use DELETE instead of GET? (I believe I can refactor my all of my routes to just /players/:id using the correct http request method if I can figure out how to pass the request method in the display.js code).

This is because your app has app.delete('/players/:id/delete') and not app.get('/players/:id/delete'). However, your server side code shouldn't change much, just one tweak:

  • If you have an app.delete() then really no need to have the verb delete at the end of the resource.

Also you need to make the request using the HTTP Method DELETE instead of making a GET in display.js. Just make the DELETE request using $.ajax() with type set to 'DELETE'

When I click the edit button, my GET /player/:id route grabs the correct information (status code 200 and preview shows JSON object) but will not populate the fields in my form.

This is because your implementation of $.each() assumes data will always be an Array and not ever and object, however, your generateForm() is passing in a Player object. Change your $.each() to do type checking for array handling and object handling. See below changes.

When I try to submit a new record, I get a 400 status code with validation error saying Path age is required. It's actually the same error for each field name, so I assume that I'm not passing the values from the field into the JSON object correctly.

If you make the above change to generateForm() it should fix this.


Just take the below snippets and replace those sections within your app, it should work.

server.js

app.delete('/players/:id', (req, res) => {
  var id = req.params.id;
  if (!ObjectID.isValid(id)) {
    return res.status(404).send();
  }
  return Player.findByIdAndRemove(id).then((player) => {
    if (player) {
      res.status(200).json(player);
    } else {
      return res.status(404).send();
    }
  }).catch((e) => {
    res.status(400).send();
  });
});

app.patch('/players/:id', (req, res) => {
  var id = req.params.id;
  var body = _.pick(req.body, ['playername', 'age', 'city', 'country', 'gender', 'handedness', 'broom', 'position', 'team', 'favoritecolor', 'headshot']);
  if (!ObjectID.isValid(id)) {
    return res.status(404).send();
  }
  Player.findByIdAndUpdate(id, {$set: body}, {new: true}).then((player) => {
    if (!player) {
      return res.status(404).send();
    } else {
      res.status(200).json(player);
    }
  }).catch((e) => {
    res.status(400).send();
  })
});

app.post('/players', (req, res) => {
  var player = new Player({
    playername: req.body.playername,
    age: req.body.age,
    city: req.body.city,
    country: req.body.country,
    gender: req.body.gender,
    handedness: req.body.handedness,
    broom: req.body.broom,
    position: req.body.position,
    team: req.body.team,
    favoritecolor: req.body.favoritecolor,
    headshot: req.body.headshot
  });

  return player.save().then((doc) => {
    return res.status(200).json(doc);
  }, (e) => {a
    return res.status(400).send(e);
  });
});

display.js

// Handle form submit event for both update & create
// if the ID_FIELD is present the server would update the database otherwise the server would create a record in the database
$('#container').on('submit', '#my-form', function(event) {
    var id = $('#id').val();
    let formData = {};

    $.each($('#myForm').serializeArray(), function(_, kv) {
          if (formData.hasOwnProperty(kv.name)) {
            formData[kv.name] = $.makeArray(formData[kv.name]);
            formData[kv.name].push(kv.value);
          }
          else {
            formData[kv.name] = kv.value;
          }
        });

    console.log(id);
    if (id != "") {
        event.preventDefault();
        $.ajax({
            url: baseURL + '/players/' + id,
            type: 'PATCH',
            success: function(edited) {
                // Handle returned edited player
            }
        })
    } else {
        event.preventDefault();
        $.ajax({
            url: baseURL + '/players',
            type: 'POST',
            success: function(created) {
              // Handle created player
            }
        })
    }
});

// Handle table click event for delete
$('#container').on('click', '.delete', function(event) {
    var id = $(this).val();
    // change the url parameters based on your API here
    // remember to create delete functionality on the server side (Model and Controller)
    // Using an JQuery AJAX GET request to get data form the server
    $.ajax({
        url: baseURL + '/players/' + id,
        type: 'DELETE',
        success: function(data) {
          //Generate table again after delete
        //change the url based on your API parameters here
        // Using an JQuery AJAX GET request to get data from the server
        $.getJSON(baseURL+'/players', function(data) {
            generateTable(data, $('#container'));
        });
        }
    });
});

// function to generate form
function generateForm(data, target){
    clearContainer(target);
    //Change form according to your fields
    $(target).append('<form id="my-form"></form>');
    var innerForm = '<fieldset><legend>Player Form</legend><p><label>Player Name: </label>'+'<input type="hidden" name="id" id="id"/>'+'<input type="text" name="playername" id="playername" /></p>' + '<p><label>Age: </label><input type="text" name="age" id="age" /></p>'+ '<p><label>Hometown: </label><input type="text" name="city" id="city" />'+ ' ' + '<input type="text" name="country" id="country" /></p>' + '<p><label>Gender: </label><input type="text" name="gender" id="gender" /></p>'+ '<p><label>Handedness: </label><input type="text" name="handedness" id="handedness" /></p>'+ '<p><label>Broom: </label><input type="text" name="broom" id="broom" /></p>'+ '<p><label>Position: </label><input type="text" name="position" id="position" /></p>'+ '<p><label>Team: </label><input type="text" name="team" id="team" /></p>'+ '<p><label>Favorite Color: </label><input type="text" name="favoritecolor" id="favoritecolor" /></p>'+ '<p><label>Headshot: </label><input type="text" name="headshot" id="Headshot" /></p>'+ '<input type="submit"/>';
    $('#my-form').append(innerForm);

    //Change values according to your data
    if(data instanceof Array){
        $.each(data, function(index, val) {
            $('#id').val(val._id);
            $('#playername').val(val.playername);
            $('#age').val(val.age);
            $('#city').val(val.city);
            $('#country').val(val.country);
            $('#gender').val(val.gender);
            $('#handedness').val(val.handedness);
            $('#broom').val(val.broom);
            $('#position').val(val.position);
            $('#team').val(val.team);
            $('#favoritecolor').val(val.favoritecolor);
            $('#Headshot').val(val.headshot);
        });
    }
    else if (typeof data === 'object') {
        $.each(data, function(key, value) => {
            $('#' + key).val(value);
        });
    }
}
peteb
  • 18,552
  • 9
  • 50
  • 62
  • This definitely fixes the DELETE issue. Thanks! JSON object issue, when using JSON.parse() I get a SyntaxError that says "unexpected token o in json at position 1". Could it be that I need to use JSON.stringify() instead? When attempting that, the app moves from the table to the form, but produces a TypeError that says "Cannot use 'in' operator to search for 'length' in...". Thoughts? – SepticReVo Mar 02 '17 at 19:50
  • @SepticReVo try replacing all the places where you doing `res.send()` with `res.status(200).json()` when a successful action occurs. This will guarantee that the `Content-Type` header will be set to `application/json`. All responses are strings, that is why you need to do JSON.parse(). Doing `JSON.stringify()` on a JSON string will just result in a doubly Stringified JSON value. – peteb Mar 02 '17 at 20:01
  • No change. JSON.parse() still gives the syntax error and JSON.stringify() still gives the TypeError. – SepticReVo Mar 02 '17 at 20:11
  • 1
    @SepticReVo see my edits for `$.each()`, Also removed my `JSON.parse()` stuff because I didn't realize `$.getJSON()` implicitly converts JSON to a JS Object. – peteb Mar 02 '17 at 20:22
  • That solves my GET /players/id issue. Thanks! I still get the 400 issue when I make a PATCH or POST requests. I realized that the value for the hidden id in the form is id and not _id, but still no dice in working overall. – SepticReVo Mar 02 '17 at 20:40
  • @SepticReVo thats because you're using `submitForm()` and that won't use `PATCH` and you don't have a `method` attribute on your `
    `. Switch to using the `$.ajax()` approach for these things as well. See my updates to both `display.js` and `server.js`
    – peteb Mar 02 '17 at 21:08
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/137087/discussion-between-septicrevo-and-peteb). – SepticReVo Mar 02 '17 at 21:31
0

Try res.json(player); instead of res.send(player); in order to send the data in JSON format.

Also, instead of $.post(url, form.serialize(), function(data) {try the code from this answer in order to send the data in JSON format.

Community
  • 1
  • 1
kbariotis
  • 783
  • 1
  • 12
  • 25