Dennis
Dennis

Reputation: 497

can't understand routing in node/express4

Please, help to clear up the routing issue:

var express = require('express');
var path = require('path');
var favicon = require('serve-favicon');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
var methodOverride = require('method-override');
var router = express.Router();

var app = express();

// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.engine('html', require('ejs').renderFile);
app.set('view engine', 'html');

// uncomment after placing your favicon in /public
//app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')));
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(methodOverride());
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));

// data storage in memory
var widgets = [
  {id: 1,
   name: 'My special widget',
   price: 100.00,
   descr: 'A widget beyond price'},
];

// routes

app.use(router);

router.get('/', function(req, res, next) {
  res.render('index');
});

router.get('/widgets', function(req, res) {
  res.send(widgets);
});

router.post('/widgets/add', function(req, res) {
  var indx = widgets.length + 1;
  widgets[widgets.length] = {  id: indx,
                               name: req.body.widgetname,
                               price: parseFloat(req.body.widgetprice),
                               descr: req.body.widgetdesc
                            };
  console.log('added ' + widgets[indx - 1]);
  res.send('Widget ' + req.body.widgetname + ' added with id ' + indx);  
});

router.get('/widgets/:id', function(req, res) {
  var indx = parseInt(req.params.id) - 1;
  if (!widgets[indx]) {
    res.send('There is no widget with id of ' + req.params.id);
  } else {
    res.send(widgets[indx]);
  }
});

router.put('/widgets/:id/update', function(req, res) {
  var indx = req.params.id - 1;
  widgets[indx] = {id: indx,
                   name: req.body.widgetname,
                   price: parseFloat(req.body.widgetprice),
                   descr: req.body.widgetdesc};
  console.log(widgets[indx]);
  res.send('updated ' + req.params.id);
});

router.delete('/widgets/:id/delete', function(req, res) {
  var indx = req.params.id - 1;
  delete widgets[indx];
  console.log('deleted ' + req.params.id);
  res.send('deleted ' + req.params.id);  
});

// catch 404 and forward to error handler
app.use(function(req, res, next) {
  var err = new Error('Not Found');
  err.status = 404;
  next(err);
});

// error handlers

// development error handler
// will print stacktrace
if (app.get('env') === 'development') {
  app.use(function(err, req, res, next) {
    res.status(err.status || 500);
    res.render('error', {
      message: err.message,
      error: err
    });
  });
}

// production error handler
// no stacktraces leaked to user
app.use(function(err, req, res, next) {
  res.status(err.status || 500);
  res.render('error', {
    message: err.message,
    error: {}
  });
});


module.exports = app;

I have 3 html files in VIEWS folder: 1 - index

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="utf-8" />
	<title>Widgets</title>
</head>
<body>

	<form action="/widgets/add" method="POST" enctype="application/x-www-form-urlencoded">
		
		<p>Widget name:<input type="text" name="widgetname" id="widgetname" size="25" required /></p>

		<p>Widget price:<input type="text" name="widgetprice" id="widgetprice" size="25" required
			pattern="^\$?([0-9]{1,3},([0-9]{3},)*[0-9]{3}|[0-9]+)(.[0-9][0-9])?$" /></p>

		<p>Widget description: <br/> <textarea name="widgetdesc" id="widgetdesc" cols="20"
			rows="5">Describe widget</textarea></p>
		<p>
			<input type="submit" name="submit" id="submit" value="Submit" />
			<input type="reset" name="reset" id="reset" value="Reset" />
		</p>

	</form>

</body>
</html>

2 - update

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="utf-8" />
	<title>Widgets</title>
</head>
<body>

	<form method="POST" action="/widgets/1/update" enctype="application/x-www-form-urlencoded">
		
		<p>Widget name:<input type="text" name="widgetname" id="widgetname" size="25" value="Me special widget" required /></p>

		<p>Widget price:<input type="text" name="widgetprice" id="widgetprice" size="25" required
			pattern="^\$?([0-9]{1,3},([0-9]{3},)*[0-9]{3}|[0-9]+)(.[0-9][0-9])?$" value="100.00" /></p>

		<p>Widget description: <br/> <textarea name="widgetdesc" id="widgetdesc" cols="20"
			rows="5">A widget beyond price</textarea></p>

		<input type="hidden" value="put" name="_method" />

		<p>
			<input type="submit" name="submit" id="submit" value="Submit" />
			<input type="reset" name="reset" id="reset" value="Reset" />
		</p>

	</form>

</body>
</html>

3 - delete

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="utf-8" />
	<title>Widgets</title>
</head>
<body>
	<p>Are you sure you want to delete widget?</p>
	
	<form method="POST" action="/widgets/1/delete" enctype="application/x-www-form-urlencoded">
		
		<input type="hidden" value="delete" name="_method" />
		
		<p>
			<input type="submit" name="submit" id="submit" value="Delete Widget" />
		</p>

	</form>

</body>
</html>

The trouble is that PUT and DELETE show error and I can not undersatnd what should I do in order to correct it.

Upvotes: -1

Views: 35

Answers (1)

JoshWillik
JoshWillik

Reputation: 2645

There are a few problems with this example. I'll address them from most to least important.

Method confusion

REST, or REpresentational State Transfer, defines what are called HTTP verbs with different purposes.

  • GET: Fetches a resource
  • POST: Creates a resource
  • PUT: Overwrites a resource
  • PATCH: Partially modifies a resource
  • DELETE: Removes a resource
  • And some others

One thing to note is that in express routers a GET request will not be matched by a POST URL handler even if the URL is the same. Even though the URL is the same, the action is different.

So the reason you are seeing a 404 error when you try to POST to /widgets/1/update is because you did not define a POST handler, you defined a PUT handler.

Improper REST URLs

Because you can have multiple actions for the same URL, a sort of standard has arisen. Since URLs are designed to refer to resources, not actions, try to keep action words out of the URL.

For example. Instead of having a PUT handler for /widgets/1/update, just do a PUT handler for /widgets/1. The fact that you're updating is already obvious because you're doing a PUT request.

In the same way, make a DELETE handler for /widgets/1 instead of /widgets/1/delete.

Keep in mind that this will not collide with a GET or DELETE handler for /widgets/1 because the verb is different.

The POST is a bit different. Instead of POST /widgets/add, use POST /widgets.

To picture this, if you were to verbalize what you want to do:

Create a record in widgets

or

POST to /widgets

The "add" or "create" is already obvious because it is a POST request.

Code order

This is a small gripe. You are .use()ing the router before you actually define the routes. This works because of how javascript handles object variables, but it looks backwards.

Understanding Express

app in your code can handle .post(), .get(), etc. There is no reason to construct and use a router in your code.

express.Router() is used when you want to mount a whole bunch of paths on a partial URL or keep a group of URLs mentally seperate For example:

var app = express()
var apiRoutes = require("./api-router")
var frontendRoutes = require("./frontend-router")

app.use("/api", apiRoutes)
app.use(frontendRoutes)

Upvotes: 1

Related Questions