I am writing an application in Express.js with a separate controller layer and a service layer. Here is my current code:
user.service.js
exports.registerUser = async function (email, password) {
const hash = await bcrypt.hash(password, 10);
const countUser = await User.countDocuments({email: email});
if(countUser > 0) {
throw ({ status: 409, code: 'USER_ALREADY_EXISTS', message: 'This e-mail address is already taken.' });
}
const user = new User({
email: email,
password: hash
});
return await user.save();
};
exports.loginUser = async function (email, password) {
const user = await User.findOne({ email: email });
const countUser = await User.countDocuments({email: email});
if(countUser === 0) {
throw ({ status: 404, code: 'USER_NOT_EXISTS', message: 'E-mail address does not exist.' });
}
const validPassword = await bcrypt.compare(password, user.password);
if (validPassword) {
const token = jwt.sign({ email: user.email, userId: user._id }, process.env.JWT_KEY, { expiresIn: "10s" });
return {
token: token,
expiresIn: 3600,
userId: user._id
}
} else {
throw ({ status: 401, code: 'LOGIN_INVALID', message: 'Invalid authentication credentials.' });
}
};
user.controller.js
exports.userRegister = async function (req, res, next) {
try {
const user = await UserService.registerUser(req.body.email, req.body.password);
res.status(201).json({ data: user });
} catch (e) {
if(!e.status) {
res.status(500).json( { error: { code: 'UNKNOWN_ERROR', message: 'An unknown error occurred.' } });
} else {
res.status(e.status).json( { error: { code: e.code, message: e.message } });
}
}
}
exports.userLogin = async function (req, res, next) {
try {
const user = await UserService.loginUser(req.body.email, req.body.password);
res.status(200).json({ data: user });
} catch (e) {
if(!e.status) {
res.status(500).json( { error: { code: 'UNKNOWN_ERROR', message: 'An unknown error occurred.' } });
} else {
res.status(e.status).json( { error: { code: e.code, message: e.message } });
}
}
}
The code works, but requires some corrections. I have a problem with error handling. I want to handle only some errors. If another error has occurred, the 500 Internal Server Error will be returned.
1) Can I use "throw" object from the service layer? Is this a good practice?
2) How to avoid duplication of this code in each controller:
if(!e.status) {
res.status(500).json( { error: { code: 'UNKNOWN_ERROR', message: 'An unknown error occurred.' } });
} else {
res.status(e.status).json( { error: { code: e.code, message: e.message } });
}
3) Does the code require other corrections? I'm just learning Node.js and I want to write the rest of the application well.
Yes, you can throw errors from service layer, it is good practice to catch errors with try/catch block in controller
I handle this with a custom error middleware, just use a next function in a catch block.
catch (e) {
next(e)
}
Example of error middleware (for more info check docs, fill free to move a middleware to file)
app.use(function (err, req, res, next) {
// err is error from next(e) function
// you can do all error processing here, logging, parsing error messages, etc...
res.status(500).send('Something broke!')
})
From my point of view it looks good. If you looking for some best practice and tools, try eslint (with AirBnb config for example) for linting, dotenv for a environment variables management, also check Node.js Best Practice
i want to give you an example:
this code in your controller
findCar(idCar)
} catch (error) {
switch (error.message) {
case ErrorConstants.ELEMENT_NOT_FOUND('LISTING'): {
return {
response: {
message: ErrorMessages.ELEMENT_NOT_FOUND_MESSAGE('LISTING'),
},
statusCode,
}
}
default: {
return {
response: {
message: ErrorMessages.UNKNOWN_ERROR_MESSAGE,
},
statusCode,
}
}
}
}
and this code in your service
findCar: async listingId => {
try {
if (some condition) {
throw new Error(ErrorConstants.ELEMENT_NOT_FOUND('LISTING'))
}
return { ... }
} catch (error) {
console.error(error.message)
throw new Error(ErrorConstants.UNKNOWN_ERROR)
}
},
controller is going to catch the service's errors
Related
I am new to Node JS. I am practising to build a MERN app. I have two function getUserById and isSignedIn. These two function are middlewares.
router.param("userId",getUserById)
const getUserById = (req,res,next,id)=>{
User.findById(id).exec((err,user)=>{
if(err){
return res.json({
error:"Unable to process request",
status: false,
})
}
console.log(1234);
if(!user){
return res.json({
error:"User doesn't exist",
status: false
})
}
req.user=user;
next()
})
}
const isSignedIn = (req,res,next)=>{
const token = req.headers.authorization.split(" ")[1]
jwt.verify(token, process.env.SECRET_KEY, (err, decoded)=>{
if(err){
return res.json({
status: false,
error: "Invalid Token"
})
}
console.log(123);
req.auth=decoded
next()
})
};
router.post("/api/create/:userId",isSignedIn,(req,res)=>{ res.send("Success")})
This is my understanding. If in url userId is found getUserById will be executed and then isSigned in. In getUserById if there was an error or if the user doesn't exist if will send a response and the execution of code stop there. But in isSignedin if the token is not valid I am sending a response as Invalid Token and the code execution should stop there. But the code after if is also getting executed why it is so?
Try following code
router.param("userId",getUserById)
const getUserById = async (req,res,next,id)=>{
try {
const user = await User.findById(id)
if(!user){
return res.json({
error:"User doesn't exist",
status: false
})
} else {
req.user=user;
next()
}
} catch (err) {
return res.json({
error:"Unable to process request",
status: false,
error: JSON.stringify(err),
})
}
}
const isSignedIn = async (req,res,next)=>{
const token = req.headers.authorization.split(" ")[1]
try {
const decoded = await jwt.verify(token, process.env.SECRET_KEY)
if (decoded) {
req.auth=decoded
next()
}
} catch (err) {
if(err){
return res.json({
status: false,
error: "Invalid Token"
})
}
}
};
router.post("/api/create/:userId",isSignedIn,(req,res)=>{ res.send("Success")})
I am new to Passport and to TSOA. I am trying to migrate an old Express route to TSOA controllers but I have not any idea how to achieve this. I did not find any documentation regarding this, in fact, I found this issue but I think I need a workaround.
router.post(
'/register',
(req: Request, res: Response, next) => {
const user: IUserData = req.body
if (validator.isEmpty(email))
return res.status(422)
.json({ error: 'Email is mandatory' })
if (validator.isEmpty(password.trim()))
return res.status(422)
.json({ error: 'Password is mandatory' })
next()
},
passport.authenticate('local'),
async (req: Request, res: Response) => {
if (req.user)
return res.json({ message: 'Already logged in' })
const existingUser: IUser = await UserModel.findOne({ email: req.body.email })
if (existingUser)
return res.status(400)
.json({ error: 'User exists' })
const data: IUserData = req.body
try {
const hashedPassword = await bcrypt.hashSync(data.password, parseInt(process.env.SALT_ROUNDS) || 10)
data.password = hashedPassword
} catch(error) {
return res.status(500)
.json({ error: error.message })
}
try {
const user: IUser = await new User(data)
await user.save()
res.json(user);
} catch(error) {
return res.status(500)
.json({ error: error.message });
}
return;
}
);
I have tried to "replicate" this code into a TSOA controller but I don't know how to apply the middleware stuff:
#Route('/auth')
export class AuthController extends Controller {
#Post()
public async register(#BodyProp() user: IUserData) {
const errors: Array<IAuthError> = []
if (validator.isEmpty(user.emailAddress)) {
this.setStatus(422)
return { error: 'Email is mandatory' }
}
if (validator.isEmpty(user.password.trim())) {
this.setStatus(422)
return { error: 'Password is mandatory' }
}
}
}
// to be continued ...
I could also separate this certain route in a different file (without any controller) but I don't know if that would work at all. I guess not.
How should I manage this?
Edit:
I just have read this article regarding TSOA authentication and it seems to handle authentication by using middlewares.
I am building a REST API using Node JS and Express JS.
I have a authService.js file that has the following function.
const verifyVerificationToken = async ({ email, token }) => {
try {
let user = await userService.findUserByEmail(email).data
if (! user) {
return {
error: true,
code: 400,
message: "Invalid account."
}
}
let tokenModel = await VerificationToken.findOne({
where: {
userId: {
[Op.eq]: user.id
},
verificationToken: {
[Op.eq]: token
}
}
})
if (! tokenModel) {
return {
error: true,
code: 400,
message: "Invalid token."
}
}
// check if the token is expired
let now = new Date();
if (now.getTime() < tokenModel.expiresAt.getTime()) {
// check if the token is already verified
if (tokenModel.verifiedAt) {
// already verified
return {
error: true,
message: "Token has already been used",
code: 400
}
}
// update the verifiedAt column
await VerificationToken.update({
verifiedAt: now
}, {
where: {
id: {
[Op.eq]: tokenModel.id
}
}
})
return {
error: false
}
} else {
// token is expired
return {
error: true,
code: 400,
message: "Token been expired."
}
}
} catch (e) {
return {
error: true,
code: 500,
message: e.message
}
}
}
As you can see, it is invoking findUserByEmail function of userService.js file. The following is the implementation of findUserByEmail function.
const findUserByEmail = async (email) => {
try {
let user = await User.findOne({
where: {
email: {
[Op.eq]: email
}
}
})
if (user) {
return {
error: false,
data: user
}
} else {
return {
error: true,
data: null,
message: "User not found",
code: 404
}
}
} catch (e) {
return {
error: true,
message: e.message,
code: 500
}
}
}
When I run the code, it never returns the user, instead in the console, I am getting the following error.
(node:7600) UnhandledPromiseRejectionWarning: Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
at ServerResponse.setHeader (_http_outgoing.js:467:11)
at ServerResponse.header (C:\Users\Acer\Desktop\collect_api\node_modules\express\lib\response.js:771:10)
at ServerResponse.send (C:\Users\Acer\Desktop\collect_api\node_modules\express\lib\response.js:170:12)
at ServerResponse.json (C:\Users\Acer\Desktop\collect_api\node_modules\express\lib\response.js:267:15)
at login (C:\Users\Acer\Desktop\collect_api\controllers\authController.js:38:19)
at processTicksAndRejections (internal/process/task_queues.js:88:5)
What is wrong with my code and how can I fix it?
As for the error "never returns the user", you should change :
let user = await userService.findUserByEmail(email).data
to :
let user = (await userService.findUserByEmail(email)).data
the error means you have sent data to the client at least twice.
If you use express, it means you have called res.send (or equivalent res.render) multiple times.
therefore you have not given the right code sample in your question for me to help more.
trying to test nodejs module.export function that will return based on request but it throws below error any idea what is implemented wrong here its expecting error >
v1Transform.js
module.exports = async (req, res) => {
try {
const validateResponse = responseHandler(req.drugPriceResponse);
} catch (error) {
if (error instanceof AppError) {
res.status(error.response.status).send(error.response.payload);
} else {
res.status(500).send(defaultErrorResponse);
}
}
}
main.test.js
describe('v1Transform()', () => {
it('should return error if accounts are ommitted', () => {
try {
v1Transform(req);
} catch (error) {
expect(error.response).to.deep.equal({
status: 500,
payload: {
'status': 500,
'title': 'Internal Server Error',
'detail': 'Accounts are not valid'
}
});
}
});
});
Error
1) v1Transform()
should return error if prices are ommitted:
AssertionError: expected undefined to deeply equal { Object (status, payload) }
i'm trying to create a user from server side using meteor js, when testing the code it show me this error in navigator console Error invoking Method 'addNewUser': Internal server error [500].
this is my code:
/imports/api/users/methods.js :
Meteor.methods({
addNewUser(user) {
console.log(user);
const email = user.emails[0].adresse;
const newUser = {
username: user.username,
email: email,
password: user.password
};
Accounts.createUser(newUser, (err) => {
if (err) {
toastr.error(err.reason,'faild');
// this.done(new Error("Submission failed"));
} else {
toastr.success('Registred with success !', 'welcome');
FlowRouter.go('home');
}
});
}
});
/imports/ui/pages/create-account.js:
AutoForm.hooks({
formCreateAccount: {
onSubmit(user) {
Meteor.call('addNewUser', user);
return false;
}
}
});