I have a problem with my routes in an Express application. This is my app.js:
var createError = require('http-errors');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');
var mongoose = require('mongoose');
var methodOverride = require("method-override");
var indexRouter = require('./routes/index');
var categoriesRouter = require('./routes/categories');
var productsRouter = require('./routes/products');
var app = express();
// create default connection to the database and binds to the error event
var mongoDB = 'mongodb srv:// ...';
mongoose.connect(mongoDB, { useNewUrlParser: true , useUnifiedTopology: true});
var db = mongoose.connection;
db.on('error', console.error.bind(console, 'MongoDB connection error:'));
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(methodOverride('_method'))
app.use('/', indexRouter);
app.use('/categories', categoriesRouter);
app.use('/products', productsRouter);
app.use(express.static(path.join(__dirname, 'public')));
// catch 404 and forward to error handler
app.use(function(req, res, next) {
next(createError(404));
});
// error handler
app.use(function(err, req, res, next) {
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};
// render the error page
res.status(err.status || 500);
res.render('error');
});
module.exports = app;
All the links in the route/index.js and route/categories.js files work as expected. I do include the route/categories.js file anyways, in case it is related to my problem.
This is route/categories.js:
var express = require('express');
var category_controller = require('../controllers/categoryControllers');
var router = express.Router();
router.get('/', category_controller.category_listing_get);
router.get('/create', category_controller.category_create_get);
router.post('/create', category_controller.category_create_post);
router.get('/:id', category_controller.category_update_get);
router.put('/:id', category_controller.category_update_post);
router.delete('/:id', category_controller.category_delete_post);
module.exports = router;
And this is routes/products.js:
var express = require('express');
var product_controller = require('../controllers/productControllers');
var router = express.Router();
router.get('/', product_controller.products_listing_get);
router.get('/subset/:id', product_controller.products_subset_get);
router.get('/subset/create', product_controller.products_create_get);
router.post('/subset/create', product_controller.products_create_post);
router.get('/subset/update/:id', product_controller.products_update_get);
router.put('/subset/update/:id', product_controller.products_update_post);
router.delete('/subset/:id', product_controller.products_delete_post);
module.exports = router;
This is the relevant part of controllers/productControllers.js
const Product = require('../models/product');
const Category = require('../models/category');
exports.products_create_get = function(req, res) {
res.send("get form for creating products")
};
This is models/products.js
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var ProductSchema = new Schema(
{
prod_name: {type: String, required: true, maxLength: 50},
prod_description: {type: String, required: true, maxLength: 120},
prod_category: {type: Schema.Types.ObjectId, ref: 'Category'},
prod_price: {type: String},
prod_stock: {type: String},
}
);
module.exports = mongoose.model('Product', ProductSchema);
Now, if I type on the browser address bar:
localhost:3000/products/subset/
I do get the expected result. However, if I type:
localhost:3000/products/subset/create
I do get an error message on the terminal which seems to be related to the mongo database, but I do not understand this as I am not acting on the database when requesting localhost:3000/products/subset/create.
This is the error message I get:
GET /products/subset/create - - ms - - create CastError: Cast to ObjectId failed for value "create" (type string) at path "prod_category" for model "Product" at model.Query.exec (/home/paul/proj/node_modules/mongoose/lib/query.js:4777:21) at model.Query.Query.then (/home/paul/proj/node_modules/mongoose/lib/query.js:4876:15) at exports.products_subset_get (/home/paul/proj/controllers/productControllers.js:13:14) at Layer.handle [as handle_request] (/home/paul/proj/node_modules/express/lib/router/layer.js:95:5) at next (/home/paul/proj/node_modules/express/lib/router/route.js:137:13) at Route.dispatch (/home/paul/proj/node_modules/express/lib/router/route.js:112:3) at Layer.handle [as handle_request] (/home/paul/proj/node_modules/express/lib/router/layer.js:95:5) at /home/paul/proj/node_modules/express/lib/router/index.js:281:22 at param (/home/paul/proj/node_modules/express/lib/router/index.js:354:14) at param (/home/paul/proj/node_modules/express/lib/router/index.js:365:14) { messageFormat: undefined, stringValue: '"create"', kind: 'ObjectId', value: 'create', path: 'prod_category', reason: BSONTypeError: Argument passed in must be a string of 12 bytes or a string of 24 hex characters or an integer at new BSONTypeError (/home/paul/proj/node_modules/bson/lib/error.js:41:28) at new ObjectId (/home/paul/proj/node_modules/bson/lib/objectid.js:67:23) at castObjectId (/home/karima/paul/proj/node_modules/mongoose/lib/cast/objectid.js:25:12) at ObjectId.cast (/home/paul/proj/node_modules/mongoose/lib/schema/objectid.js:246:12) at ObjectId.SchemaType.applySetters (/home/paul/proj/node_modules/mongoose/lib/schematype.js:1189:12) at ObjectId.SchemaType._castForQuery (/home/paul/proj/node_modules/mongoose/lib/schematype.js:1623:15) at ObjectId.SchemaType.castForQuery (/home/paul/proj/node_modules/mongoose/lib/schematype.js:1613:15) at ObjectId.SchemaType.castForQueryWrapper (/home/paul/proj/node_modules/mongoose/lib/schematype.js:1590:20) at cast (/home/kpaul/proj/node_modules/mongoose/lib/cast.js:344:32) at model.Query.Query.cast (/home/paul/proj/node_modules/mongoose/lib/query.js:5199:12), valueType: 'string'
I have tried changing the order of the statements in the app.js file, clearing the cache, changing the name of the routes, restarting my virtual machine and I just do not understand what is happening because I just want to send a short string to the browser and am not touching the database with my request and still seem to get an error related to the database.
Note: localhost:3000/categories/create also works fine.
CodePudding user response:
The order in which you declared these two routes is causing the problem:
router.get('/subset/:id', product_controller.products_subset_get);
router.get('/subset/create', product_controller.products_create_get);
Express will not match a request to the best route (whatever that may be), but the first route.
In this case, because /subset/:id
is declared before /subset/create
, making a request for /subset/create
will match the first route.
The easiest solution is to switch their declaration so the more specific route will match first:
router.get('/subset/create', product_controller.products_create_get);
router.get('/subset/:id', product_controller.products_subset_get);
Or, if you're trying to use REST semantics, use a POST request to /subset
for creating new entities.
And if :id
will have a fixed format, you can limit the match to only requests that have a valid-looking id in their URL. For instance an ObjectId:
app.get('/subset/:id([a-fA-F0-9]{24})', …)
FWIW, the stack trace is actually pointing out that products_subset_get
was being called:
… at exports.products_subset_get (/home/paul/proj/controllers/productControllers.js:13:14)
^^^^^^^^^^^^^^^^^^^^^^^^^^^