Suppose that I have the following REST endpoints
/api/companies
/api/companies/:id
/api/companies/name/:slug
The first route /api/companies
POST will post a new company, while GET will fetch list of all companies.
The second route /api/companies/:id
GET will fetch that specific company by its id
, PUT will update it, DELETE will remove it.
The third route /api/companies/name/:slug
will fetch list of companies that start with name
.
My router.js
file looks like this
// /api/companies
router
.route('/')
.post(controllers.createCompany)
.get(controllers.getByPriority)
// /api/companies/:id
router
.route('/:id')
.put(controllers.updateOne)
.delete(controllers.removeOne)
.get(controllers.getOne)
// /api/companies/name/:slug
router
.route('/name/:slug?')
.get(controllers.getByName)
The problem is if there was no slug
parameter in the third route, it will match with the second route which will return cast error.
For example: GET /api/companies/name/
returns the following error
CastError: Cast to ObjectId failed for value "name" (type string) at path "_id" for model "company"
at model.Query.exec (D:\Projects\StackInfo\stack-info\backend\node_modules\mongoose\lib\query.js:4545:21)
at D:\Projects\StackInfo\stack-info\backend\dist\utils\crud.js:91:15
at Layer.handle [as handle_request] (D:\Projects\StackInfo\stack-info\backend\node_modules\express\lib\router\layer.js:95:5)
at next (D:\Projects\StackInfo\stack-info\backend\node_modules\express\lib\router\route.js:137:13)
at next (D:\Projects\StackInfo\stack-info\backend\node_modules\express\lib\router\route.js:131:14)
at next (D:\Projects\StackInfo\stack-info\backend\node_modules\express\lib\router\route.js:131:14)
at next (D:\Projects\StackInfo\stack-info\backend\node_modules\express\lib\router\route.js:131:14)
at next (D:\Projects\StackInfo\stack-info\backend\node_modules\express\lib\router\route.js:131:14)
at next (D:\Projects\StackInfo\stack-info\backend\node_modules\express\lib\router\route.js:131:14)
at next (D:\Projects\StackInfo\stack-info\backend\node_modules\express\lib\router\route.js:131:14) {
messageFormat: undefined,
stringValue: '"name"',
kind: 'ObjectId',
value: 'name',
path: '_id',
reason: TypeError: Argument passed in must be a Buffer or string of 12 bytes or a string of 24 hex characters
at new BSONTypeError (D:\Projects\StackInfo\stack-info\backend\node_modules\bson\lib\error.js:39:42)
at new ObjectId (D:\Projects\StackInfo\stack-info\backend\node_modules\bson\lib\objectid.js:62:23)
at castObjectId (D:\Projects\StackInfo\stack-info\backend\node_modules\mongoose\lib\cast\objectid.js:25:12)
at ObjectId.cast (D:\Projects\StackInfo\stack-info\backend\node_modules\mongoose\lib\schema\objectid.js:245:12)
at ObjectId.SchemaType.applySetters (D:\Projects\StackInfo\stack-info\backend\node_modules\mongoose\lib\schematype.js:1135:12)
at ObjectId.SchemaType._castForQuery (D:\Projects\StackInfo\stack-info\backend\node_modules\mongoose\lib\schematype.js:1567:15)
at ObjectId.SchemaType.castForQuery (D:\Projects\StackInfo\stack-info\backend\node_modules\mongoose\lib\schematype.js:1557:15)
at ObjectId.SchemaType.castForQueryWrapper (D:\Projects\StackInfo\stack-info\backend\node_modules\mongoose\lib\schematype.js:1534:20)
at cast (D:\Projects\StackInfo\stack-info\backend\node_modules\mongoose\lib\cast.js:336:32)
at model.Query.Query.cast (D:\Projects\StackInfo\stack-info\backend\node_modules\mongoose\lib\query.js:4968:12),
valueType: 'string'
}
The crud.js
getOne looks like this as well
export const getOne = model => async (req, res) => {
try {
const id = req.params.id
const doc = await model
.findOne({ _id: id })
.lean().exec() // Where the error exists
if (!doc) {
return res.status(404).end()
}
res.status(200).json({ data: doc })
} catch (e) {
console.error(e)
res.status(400).end()
}
}
I tried to add other route so that it can handle the request if no slug
was passed along
// /api/companies
router
.route('/')
.post(controllers.createCompany)
.get(controllers.getByPriority)
// /api/companies/:id
router
.route('/:id')
.put(controllers.updateOne)
.delete(controllers.removeOne)
.get(controllers.getOne)
// in case /api/companies/name/:slug did not match
router
.route('/name')
.get(controllers.getByPriority)
// /api/companies/name/:slug
router
.route('/name/:slug?')
.get(controllers.getByName)
But it still matches with the /api/companies/:id
route and returns the same error. So, is there any solution for avoiding to route with /api/companies/:id
when I am requesting on /api/companies/name/
even with an empty parameter?
Express execute code top to bottom. So you can just reverse the order of the endpoints in the code. Since /name/:slug
is more specific, define it before /:id
:
router
.route('/')
.post(controllers.createCompany)
.get(controllers.getByPriority)
router
.route('/name/:slug?')
.get(controllers.getByName)
router
.route('/:id')
.put(controllers.updateOne)
.delete(controllers.removeOne)
.get(controllers.getOne)