Currently I am trying to write RESTapi for a website with users and flashcards. I am using a MERN stack in order to build this. The mongodb structure is the following:
//FlashCardSchema
const flashcardSchema = new Schema({
deckID: { type: Schema.Types.ObjectId, required: true },
side1: { type: String, required: true },
side2: { type: String, required: true }
}, { collection: "Flashcards" })
//FlashCardSetSchema
const flashcardDeckSchema = new Schema({
collectionID: { type: Schema.Types.ObjectId, required: true },
setTitle: { type: String, required: true },
flashcards: [flashcardSchema]
}, { collection: "Decks" });
//FlashCard DB Schemas
const flashcardCollectionSchema = new Schema({
userID: {
type: Schema.Types.ObjectId,
required: true
},
decks: [flashcardDeckSchema]
}, { collection: "FlashcardCollection" });
As you can see a flashcard collection is also connected to a user collection through its auto generated ID by mongodb. This means each user has only one "flashcardcollection" which is generated the same time a user registers.
Currently I am working on the login page and since this project is working with RESTApis I am using JWT tokens for login/authorization, and within these tokens I am passing on information such as UserID.
What I am trying to do currently is when someone tries to login, it verifies them, and finds the flashcardcollection assigned to them based on their userID. I ran into a problem of trying to call a findOne function within another findOne function and therefore I am not progressing.
Currently the login code looks as the following:
//Request email, password -> find user with email -> if no user throw error -> get password -> decrypt and compare -> if false throw error -> if true generate JWT token
//JWT secret == JWTSECRETKEY , Expires in 1 hour
exports.login = async (req, res, next) => {
const email = req.body.email;
const password = req.body.password;
let loadedUser;
let loadedFlashcardCollection;
User.findOne({ email: email }).then(user => {
if (!user) {
const error = new Error('A user with this email does not exist');
error.statusCode = 401;
throw error;
}
loadedUser = user;
return bcrypt.compare(password, user.password);
})
.then(isEqual => {
if (!isEqual) {
const error = new Error("Wrong Password");
error.statusCode = 401;
throw error;
}
loadedFlashcardCollection = getCollectionId(loadedUser._id);
//Error caused by trying to insert flashcardID into JWT token and not being able to get a respons
const token = jwt.sign({ email: loadedUser.email, userId: loadedUser._id.toString(), loadedFlashcardCollection: loadedFlashcardCollection.toString() }, 'JWTSECRETTOKEN', { expiresIn: '2h' });
res.status(200).json({ token: token, userId: loadedUser._id.toString(), collectionID: loadedFlashcardCollection.toString() })
})
.catch(err => {
if (!err.statusCode) {
err.statusCode = 500;
}
next(err);
})
}
//function to find userID async way since it was needed.
async function getCollectionId(loadedUserID) {
const collection = await flashcardCollection.findOne({ userID: loadedUserID });
if (!collection) {
error.statusCode = 401;
throw new Error(`No collection found.`);
}
return collection._id;
}
and this returns the following when I try to log in through a POSTMAN
api:
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImV4YW1wbGUyQGdtYWlsLmNvbSIsInVzZXJJZCI6IjY0NmRkZDZhMjJlMmZkYmJkYTFjYmU2OCIsImxvYWRlZEZsYXNoY2FyZENvbGxlY3Rpb24iOiJbb2JqZWN0IFByb21pc2VdIiwiaWF0IjoxNjg0OTUwNTk4LCJleHAiOjE2ODQ5NTc3OTh9.CRkMZ1m-CfZX7RLB-YXo6y9Ur_7tK4C1SPvCCQb-4ZE",
"userId": "646ddd6a22e2fdbbda1cbe68",
"collectionID": "[object Promise]"
}
can you guys help me figure out how to pass on the collectionID of the user in the same JWT token
I have tried creating async await functions but since i am new to NodeJs i am having a problem understanding how they work, therefore so far this was my closest attempt to success(at least its not throwing an error)
2
Answers
Since getCollectionId is asynchronous function and it return a promise from db, you need to wait for the response so it will be something like this,
.then( async (isEqual) => {
// your other code
loadedFlashcardCollection = await getCollectionId(loadedUser._id);
})
Firstly , On a beginner level , you need to understand the async/await and .then/catch, Async Await vs then/catch , Promise , this will give you bit insight .
We use await when we need to call and wait for async function or Promise .
In your case when, you call getCollectionId without await ,inside your getCollectionId, your function will work but your getCollectionId will not wait for the function to completely finishes.
You are getting a promise object because async functions returns Promise, since without await, getCollectionId is not waiting to settled Promise ,so ,its in pending state,which is an initial state, neither fulfilled nor rejected.
So ,inside .then()
we used async in the callback function inside .then(), because await can only be used inside async function.