SepticReVo
SepticReVo

Reputation: 45

Express Routes not functioning on Single Page Web App

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};

Upvotes: 0

Views: 154

Answers (2)

peteb
peteb

Reputation: 19428

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);
        });
    }
}

Upvotes: 1

kbariotis
kbariotis

Reputation: 802

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.

Upvotes: 0

Related Questions