Avoid repetitive functions in a controller file inside an express REST API - javascript

I have a bunch of controller functions that do exactly the same thing: call a service function in another file of the same name. For the sake of example, I'll provide just two functions, but imagine there are several of them.
const express = require('express');
const router = express.Router();
const userService = require('./user.service');
const authorize = require('_helpers/authorize');
// routes
router.post('/authenticate', authenticate);
router.post('/create', create);
// ...
// ( it goes on like this )
// ...
module.exports = router;
function authenticate(req, res, next) {
userService.authenticate(req.body)
.then(user => user ? res.json(user) : res.status(400).json({ message: 'Error.' }))
.catch(err => next(err));
}
function create(req, res, next) {
userService.create(req.body)
.then(user => user ? res.json(user) : res.status(400).json({ message: 'Error.' }))
.catch(err => next(err));
}
// ...
// ( it goes on like this )
Is there a way in Javascript to avoid such repetitive code? ( I'm not new to programming but I'm a newcomer to Javascript ). I was thinking about automating this code generation with vim macros but maybe there's some package or feature in the language that can make this code look less verbose, maybe some sort of metaprogramming.

Create two utility functions, like this
handleUser which takes a res object and returns another function that takes user. This will allow you to inject res easily
handleError which takes a next callback and return another function that takes err
const handleUser = res => user =>
user ? res.json(user) : res.status(400).json({ message: 'Error.' });
const handleError = next => err => next(err);
const authenticate = (req, res, next) =>
userService.authenticate(req.body)
.then(handleUser(res)).catch(handleError(next));
const create = (req, res, next) => userService.create(req.body)
.then(handleUser(res)).catch(handleError(next));

Related

Throw await syntax in Nodejs async express handler is not working

const express = require("express");
const expressAsyncHandler = require("express-async-handler");
const app = express();
const f = async () => {
return false;
};
app.get(
"/",
expressAsyncHandler(async () => {
throw await f();
}),
() => {
console.log("the bug!");
}
);
app.use((err, req, res, next) => {
console.log("caught!", err);
});
app.listen(4000, () => console.log("listening on port 4000..."));
Expected output on the console:
"caught!".
output:
the bug!.
question: Why? Is it a bug in async-express-handler package or is it a normal JavaScript behaviour? what if I want to throw await something inside? how ?
Your problem is that you're throwing the value false. This doesn't fit into nodejs' callback conventions (and by extension, express error handling), which requires the err parameter to get a truthy value to be considered an error. A much simpler way to reproduce the issue:
app.get(
"/",
(req, res, next) => {
Promise.reject(false).catch(next);
// or even just:
next(false);
},
() => {
console.log("the bug!");
}
);
So just don't do that! Always throw an Error, not a string, not something else, and this principle also holds for promise rejections.

Express Router doesn't route as expected

Running NodeJS on Ubuntu 20.04.2, using VSApp with the debugger
I have the following file named /src/routes/regions.js:
const router = require('express').Router()
const { int } = require('neo4j-driver')
const { required, optional } = require('../middleware/auth')
const { check } = require('express-validator')
const validate = require('../middleware/validate')
const neo4j = require('../neo4j')
const Joi = require('joi');
const Region = require('../entities/Region')
router.get('/1', (req, res, next) => {
return req.neo4j.read(`
MATCH (regions:Region)
return regions order by regions.name ASC
`, params)
.then(regions => res.send(regions))
.catch(e => next(e))
})
router.get('/', (req, res, next) => {
return req.neo4j.read(`
MATCH (regions:Region)
return regions order by regions.name DESC
`, params)
.then(regions => res.send(regions))
.catch(e => next(e))
})
router.get('/:name', (req, res, next) => {
const params = {
name: req.params ? req.params.name : null
}
return req.neo4j.read(`
MATCH (region:Region { name: $name }) return region
`, params)
.then(regions => res.send(regions))
.catch(e => next(e))
})
module.exports = router;
From a browser, if I enter localhost:3000/regions I receive the list of all the Regions in Descending order.
But if I try to enter localhost:3000/regions/1 I receive nothing. The only difference between the two calls should be the order of the received data. The same for localhost:3000/regions/Lazio
It looks like it is not able to recognize patterns in the provided URL
The other really strange behavior is that if I set a breakpoint on any line of the file, the debugger doesn't stop. It looks like it is running another program ....
Can someone help?
Your first route needs to include the name parameter. Express routes aren't inclusive of any others defined elsewhere, so you need to spell it out a bit.
router.get('/:name/1', (req, res, next) => {

Express node.js forEach route

I was trying to make a routes for each ID I using a forEach loop but It stay loading until timeout reaches, all expected values are in place, all good but the second route is not running, I was fighting it despretly until now. I made sure there is a problem.
server.js
const router = require('express').Router();
function isAuthorized(req, res, next) {
if (req.user) {
next();
}
else {
res.redirect('/login')
}
}
let myguild = [];
router.get(`*`, isAuthorized, (req, res) => {
res.status(200);
console.log("wow");
console.log(req.user.guilds.length)
req.user.guilds.forEach(guild => {
myguild.push(guild);
})
console.log("Finished");
myguild.forEach(guild => {
console.log('Started')
router.get(guild.id, (req, res) => { // here is the problem
console.log("uh")
res.send("HAMBURGER")
console.log(req, res, guild)
})
console.log("Outed")
})
});
module.exports = router;
output:
wow
23
Finished
Started
Outed
Started
Outed
Started
Outed
Star... 'there is more but this is enough'
It should behave and run within server/${guild.id} but got (failed) request
Any Ideas?
You might need to redesign the API to better fit what you're trying to accomplish. If you already know which guilds are available then you'd need to create those before the server is initialized.
Even if they come from a database or are dynamic, you can loop through the guild "options" and create endpoints then provide access to them only if the user is qualified.
const { guilds } = require('./config')
const guildHandler = (req, res) => {
// Assuming you're doing more here
res.send('Hamburger')
}
guilds.forEach(guild => router.get(`/guilds/${guildId}`, guildHandler)
Or if you are NOT doingg something different in the middleware for each guild then you could just have a single route for guild.
router.get('/guilds/:guildId, guildHandler)
Not really sure what you're trying to accomplish but checkout out the Express docs. They solve most use cases fairly easily.
https://expressjs.com/en/api.html#req
You never call res.end() from your outer res.get() handler, so the request never completes.
And, with respect, creating route handlers like that in a loop is a mistake. It will lead to real performance trouble when your app gets thousands of guilds.
You'll want to use just one route, with a named route parameter, something like this.
const createError = require('http-errors')
router.get(':guildid', isAuthorized, (req, res, next) => {
const guildid = req.params.guildid
if (req.user.guilds.includes(guild)) {
console.log("uh")
res.send("HAMBURGER").end()
console.log(req, res, guildid)
} else {
next(createError(404, guildId + ' not found'))
}
})
Thanks for everyone helped.
Inspired answer
Final Result:
server.js
router.get('/:guildid', isAuthorized, (req, res, next) => {
console.log('started')
const guildid = req.params.guildid
if (req.user.guilds.some(guild => guild.id === guildid)) {
console.log('uh')
res.send("HAMBURGER").end()
} else {
res.sendStatus(404);
}
})

I don't understand why it's looking for an ID instead of a view

I'm trying to get a to a certain route, which contains a form, but for some reason it is looking for an id. I'm going to share my routes, my views and the error.
//celebrities routes
const express = require('express');
const router = express.Router();
const Celeb = require('../model/celebrity.model')
router.get('/celebrities', (req, res) => {
Celeb.find()
.then(AlltheModels => {
console.log(AlltheModels)
res.render('celebrities/index', { celebs: AlltheModels })
})
.catch(error => console.log('error while getting the celebrities', error))
})
router.get('/celebrities/:id', (req, res) => {
const celebId = req.params.id
console.log(celebId)
Celeb.findById(celebId)
.then(OneCeleb => {
console.log(OneCeleb)
res.render('celebrities/show', { celebOne: OneCeleb })
})
.catch(error => console.log('there was an error by retrieving..', error))
})
//NEW celebrities
router.get('/celebrities/new', (req, res) => {
res.render('celebrities/new')
})
router.post('/celebrities', (req, res) => {
const { name, occupation, catchPhrase } = req.body;
Celeb.create({ name, occupation, catchPhrase })
// .then(CelebNew => {
// CelebNew.save()
// console.log(CelebNew + '...has been entered')
// })
.then(() => res.redirect('/celebrities'))
.catch(error => `There was an error of ${error}`, err)
})
module.exports = router;
Here's the view that should lead to the form view
<div>
Create a new Celebrity
</div>
<div>
{{#each celebs}}
<a href="/celebrities/{{_id}}">
<h2>{{this.name}}</h2>
</a>
{{/each}}
</div>
and here's the error
"GET /celebrities/new - - ms - -
...there was an error by retrieving.. CastError: Cast to ObjectId failed for value "new" at path "_id" for model "Celeb"
at model.Query.exec (/mnt/c/Users/carlo/documents/ironhack/labs/lab-mongoose-movies/starter-code/node_modules/mongoose/lib/query.js:4408:21)"
from what I understand, the problem lies in this route
router.get('/celebrities/:id', (req, res) => {
const celebId = req.params.id
console.log(celebId)
Celeb.findById(celebId)
.then(OneCeleb => {
console.log(OneCeleb)
res.render('celebrities/show', { OneCeleb })
})
.catch(error => console.log('there was an error by retrieving..', error))
})
but I have no clue why or where the error is, or why it is trying to look for an Id of new, is it the handlebars helpers?.
Your problem is that the path /celebrities/new matches the pattern /celebrities/:id.
When you make a GET request to the path /celebrities/new, Express looks through all of the registered routes, in order, trying to find a match. When Express finds the registered route, /celebrities/:id it considers this a match because the request path matches the pattern - it starts with "/celebrities/" and is followed by an arbitrary string value which it interprets as the id param ("new").
Express will never serve the /celebrities/new GET route because /celebrities/:id will always be the first match.
In order to have Express find the /celebrities/new route, it must be registered before the /celebrities/:id route. You literally just need to move the router.get('/celebrities/new'... code above the router.get('/celebrities/:id',... code.

Mongoose Add If To Middleware

I am not sure if this is a Mongoose or Nodejs Express error?
I would just like to know if there is a way to add middleware in the form of an if. This is my call:
app.post(pPath, auth, (req, res) => {
...
})
And I would just like to do something like this:
app.post(pPath, varBoolean ? auth : null, (req, res) => {
...
})
The above example does not work though. Any idea how I can do this?
Express methods don't support non-function handlers. This is generally a good thing because this allows to detect problems with imports on application start.
This can be achieved with a spread:
app.post(...[pPath, varBoolean && auth, (req, res) => {
...
}].filter(Boolean))
You should try using 'app.use', if you want to have a middleware in place.
app.use('/path', (req, res, next) => {
const { test } = req.body;
const { auth } = req.headers;
if(!test) {
return res.status(400).json({message: 'Missing field test'});
}
const validToken = await tokenValidation(auth);
if(!validToken){
return res.status(403).json({message: 'Unauthorized'});
}
next();
});

Categories

Resources