I decided to give typescript a try and make a small authentication API with express. What I wanted to do now is, make the username and email unique, so my mongoose schema looks as follows:
import * as mongoose from 'mongoose';
const userSchema = new mongoose.Schema({
username: {
type: String,
required: [true, 'Username is required!'],
unique: true,
min: 6,
max: 255,
},
email: {
type: String,
required: [true, 'Email is required!'],
unique: true,
min: 6,
max: 255,
},
password: {
type: String,
required: [true, 'Password is required!'],
min: 6,
max: 1024,
},
date: {
type: Date,
default: Date.now(),
},
});
export const User = mongoose.model('User', userSchema);
My controller is also very simple:
export async function registerUser(
req: express.Request,
res: express.Response,
): Promise<express.Response> {
const user = new User({
username: req.body.username,
email: req.body.email,
password: req.body.password,
});
const error = user.validateSync();
if (error) {
return res.status(400).json(error);
}
await user.save(function (err) {
if (err) {
return res.status(400).send(err);
}
return res.status(200).send({ success: true, message: 'User created!' });
});
}
This all works as expected, however, if I do a POST request, where both the email and username already exist in the database, I do not get a response with both of them in the body. For example if I make a POST request with:
{
"username": "username2",
"email": "emailblabla2",
"password": "password1234"
}
where username2 and emailblabla2 have already been used, I will first get:
{
"driver": true,
"name": "MongoError",
"index": 0,
"code": 11000,
"keyPattern": {
"username": 1
},
"keyValue": {
"username": "username2"
}
}
as a response and at the second attempt I will get the same but for the email.
Is it possible to get both at the same time? Should I even be using mongoose to validate my data? I see a lot of people recommending joi, however, I thought if mongoose is already capable of doing that, why use an additional library for it.
Related
So when i make a request to an endpoint such as this http://localhost:3000/api/auth/get-info/email#example.com
using this code
The Model looks thus :
const mongoose = require("mongoose");
const userSchema = new mongoose.Schema({
email: {
type: String,
required:true,
max: 255,
},
password: {
type: String,
required:true,
max: 2055,
},
phone: {
type: String,
required:true,
max: 255,
},
balance: {
type: mongoose.SchemaTypes.Number,
required:true,
},
fullname: {
type: String,
required:true,
max: 250,
},
created_at: {
type: Date,
default: Date.now(),
},
});
module.exports = mongoose.model('User',userSchema);
The code to utilise the model looks thus
const router = require("express").Router();
const User = require("../models/User"); // <----------- this is the model that saves the data to the Database
const bcrypt = require("bcrypt");
const jwt = require("jsonwebtoken");
router.get("/get-info/:email", async (req, res) => {
try {
if (
!req.headers.authorization ||
!req.headers.authorization.startsWith("Bearer ") ||
!req.headers.authorization.split(" ")[1]
) {
return res.status(422).json({ message: "Please Provide Token!" });
}
const user = await User.find({ email: req.params.email });
res.status(200).json(user);
} catch (error) {
res.status(404).json({ message: error.message });
}
});
module.exports = router;
i get this as response
[
{
"_id": "63a1bf35d7622f37a569405a",
"email": "john_kross2#yopmail.com",
"password": "$2b$10$LFhAxdgu9aARyMkwNWXqs.unaf.vFAfUgWjfT6uK16wF/wwDN0oFS",
"phone": "08098777600",
"balance": 0,
"fullname": "Lucas Osman",
"created_at": "2022-12-20T13:52:49.442Z",
"__v": 0
}
]
How do i make a simple request, in this case that i would be able to make a request, get the balance as response, then use the balance to add to the amount and update the data in MongoDB database. A snippet may be very useful here.
I tried to call the Data from the above, then use amount in req.body.amount and add to the data from the database and use it to update the balance. Does not work as I expect.
I am trying to populate my ChatRoom model with the User reference. However, it returns a ChatRoom object with only _ids where I expected usernames, as if I never applied populate on it.
Here is an extract of my ChatRoom model :
const ChatRoom = mongoose.model("ChatRoom", {
sender: {
type: mongoose.Schema.Types.ObjectId,
ref: "User",
},
roomname: { type: String, default: "new room" },
messages: [
{
messages: {
type: mongoose.Schema.Types.ObjectId,
ref: "Message",
},
meta: [
{
user: {
type: mongoose.Schema.Types.ObjectId,
ref: "User",
},
delivered: Boolean,
read: Boolean,
},
],
},
],
participants: [
{
user: { type: mongoose.Schema.Types.ObjectId, ref: "User" },
},
],
isPrivate: { type: Boolean, default: "false" },
});
My User model :
const User = mongoose.model("User", {
username: { required: true, unique: true, type: String },
avatar: Object,
token: String,
hash: String,
salt: String,
chatroom: {
type: mongoose.Schema.Types.ObjectId,
ref: "ChatRoom",
},
});
As this seems to be a recurrent issue, I tested several StackOverflow answers for my populate code :
Using populate('participants.user') and 'model: user' or just populate('participants.user'), same solution here:
const chatroom = await ChatRoom.findById(req.params.id)
.populate([
{
path: "participants.user",
model: "User",
},
])
.exec((err, user) => {
if (err) {
console.log("error", err);
} else {
console.log("Populated User " + user);
}
});
The console.log returns :
Populated User { _id: new ObjectId("62262b342e28298eb438d9eb"),
sender: new ObjectId("6225d86c9340237fe2a3f067"), roomname:
'Hosmeade', participants: [ { _id: new
ObjectId("6225d86c9340237fe2a3f067") } ], isPrivate: false,
messages: [], __v: 0 }
As if no populate method was ever applied. On the client side, I get an empty string.
Checking my documents aren't empty, this link mentions that Mongoose get problems with detecting referring model across multiple files but the solution doesn't work for me :
_id:6225d86c9340237fe2a3f067 username:"Berlioz" token:"rTCLAiU7jq3Smi3B"
hash:"wvJdqq25jYSaJjfiHAV4YRn/Yub+s1KHXzGrkDpaPus="
salt:"hZdiqIQQXGM1ryYK" avatar:Object
__v:0
If I remove the .exec(...) part, I get the info on the client side, but participants is still filled with only id :
chatroom response : Object { _id: "62262bb14e66d86fb8a041e8",
sender: "6225d86c9340237fe2a3f067", roomname: "Very secret room",
participants: (1) […], isPrivate: false, messages: [], __v: 0 }
I also tried with select: 'username' and get the same result as above :
const chatroom = await ChatRoom.findById(req.params.id).populate({
path: "participants.user",
select: "username",
});
Populating it "as an array"
Changing type of participants.user in my ChatRoom model into an Object (nothing changes)
If needed hereafter are my repos:
Client side and Backend
I run out of ideas on how to debbug my code. Any help would be great !
This model allow me to insert new users using Postman
sequelize.define('users', {
id: {
type: S.INTEGER,
allowNull: false,
autoIncrement: true,
primaryKey: true
},
name: {
type: S.STRING,
allowNull: false,
validate: {
notNull: {
msg: 'Name is mandatory'
},
len: {
args: [2, 30],
msg: 'Name field must to be at least 2 characters long.'
},
isAlpha: true
}
},
lastName: {
type: S.STRING,
allowNull: false,
validate: {
notNull: {
msg: 'Lastname is mandatory.'
},
len: {
args: [2, 50],
msg: 'Lastname field must to be at least 2 characters long.'
},
isAlpha: true,
},
}
})
}
This is the express route i design
Another weird thing is that the .findOrCreate function doesn't work neither!
I saw myself forced to divide the system in .findOne and .create functions.
server.post('/', async (req, res)=>{
try {
const { name, lastName } = req.body;
if(!name || !lastName){
res.send('All fields must to be completed')
}
const user = await Users.findOne({
where: {
email: email
}
})
if(user){
return res.send('This user already exists, choose a different one!').status(100);
}
const createUser = await Users.create({
name: name,
lastName: lastName
})
return res.send(createUser)
}
catch (err) {
res.send({ data: err }).status(400);
}
})
The situation is that postman works properly but psql terminal throws the following error when i try to insert data to the model:
insert into users (name, lastName)
values('John', 'Doe', 'jdoe#gmail.com', '12345678', 'wherever');
ERROR: no existe la columna «lastname» en la relación «users»
LÍNEA 1: insert into users (name, lastName)
If you really want to make your life hard with upper case letters in your identifiers, you have to surround them with double quotes whenever you use them in SQL:
"lastName"
Let me begin by saying I know that this seems to be a frequently asked question and I've spent a couple of days trying to figure it out but no answer seems to work so I'm trying on here.
I have two models, User and Chapter: a Chapter can have have many members (Users). When I do router.get('/chapters') I want to see an array of all the Users associated with a Chapter as a property along with the other Chapter properties, like so:
[
{
"leads": [],
"members": [
{"_id":"someString1","firstName":"...", "lastName":"..."},
{"_id":"someString2","firstName":"...", "lastName":"..."},
],
"date": "2018-12-12T15:24:45.877Z",
"_id": "5c11283d7d13687e60c186b3",
"country": "5c11283d7d13687e60c185d6",
"city": "Buckridgestad",
"twitterURL": "qui",
"bannerPic": "http://lorempixel.com/640/480/people",
"__v": 0
}
]
But what I'm getting is this:
[
{
"leads": [],
"members": [],
"date": "2018-12-12T15:24:45.877Z",
"_id": "5c11283d7d13687e60c186b3",
"country": "5c11283d7d13687e60c185d6",
"city": "Buckridgestad",
"twitterURL": "qui",
"bannerPic": "http://lorempixel.com/640/480/people",
"__v": 0
}
]
These are my Schemas:
Chapter
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
// Create Schema
const ChapterSchema = new Schema({
country: {
type: Schema.Types.ObjectId,
ref: "countries"
},
city: {
type: String,
required: true
},
leads: [
{
type: Schema.Types.ObjectId,
ref: "users"
}
],
members: [
{
type: Schema.Types.ObjectId,
ref: "users"
}
],
twitterURL: {
type: String,
required: true
},
bannerPic: {
type: String,
required: true
},
date: {
type: Date,
default: Date.now()
}
});
module.exports = Chapter = mongoose.model("chapters", ChapterSchema);
User
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
// Create Schema
const UserSchema = new Schema({
username: {
type: String,
required: true
},
firstName: {
type: String,
required: true
},
lastName: {
type: String,
required: true
},
organisation: {
type: String,
required: true
},
chapter: {
type: Schema.Types.ObjectId,
ref: "chapters"
},
email: {
type: String,
required: true
},
admin: {
type: Boolean,
default: false
},
lead: {
type: Boolean,
default: false
},
password: {
type: String,
required: true
},
date: {
type: Date,
default: Date.now()
}
});
module.exports = User = mongoose.model("users", UserSchema);
Like I said, when I call the endpoint, I want it to return me all the Chapters with all the Users as a populated property.
I've tried a lot of variations of .populate() but to know luck. The closest I got was going through the early levels of callback hell which I know isn't necessary with today's tech, but nothing is working!
My routes/api/chapters.js
// #route GET api/chapters
// #desc Get all Chapters
// #access Public
router.get("/", (req, res) => {
Chapter.find()
.populate("members")
.then(chapters => {
return res.json(chapters);
})
.catch(err =>
res.status(404).json({ nochaptersfound: "No Chapters found" })
);
});
I can get it to work the other way around:
My routes/api/users.js
// #route GET api/users
// #desc Return all users
// #access Public
router.get("/", (req, res) => {
User.find()
.populate("chapter")
.exec()
.then(users => res.status(200).json(users))
.catch(err => console.log(err));
Returns a user with the populated Chapter, but I can't populate the chapter.members array
Any help would be greatly appreciated!
Thanks!!
From your comment, I believe you are not actually storing users in your chapters. What you are doing is this:
User.create({..., chapter: id})...
And assuming chapter now has a user. Its not the way it works with mongoose, so if you want to actually save in both place, you will need to do it yourself. You are thinking about this as if it were a relational database
You will need to do something like:
const user = await User.create({..., chapter: id})
const chapter = await Chapter.findOne({ _id: id })
chapter.members.push(user)
chapter.save()
If your populate wasn't working, you'd not get an empty array, you'd get an array with ids. Your current populate query is fine, you just don't have any data to populate
With promises, it would look like this:
var userPromise = User.create({..., chapter: id}).exec()
var chapterPromise = Chapter.findOne({ _id: id }).exec()
Promise.all([userPromise, chapterPromise]).then((user, chapter) => {
chapter.members.push(user)
return chapter.save()
}).then(chapter => {
// send response
})
If you need 10 chapters with 10 to 50 users, I'd create 50 users, then push all of them into the chapters and save the chapter.
I'm (trying) to build an API system using mongoose, express and node. Right now I've two Schemas: user and event.
User Schema:
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var userSchema = new Schema({
name: {
type: String,
required: true
},
email: {
type: String,
required: true,
unique: true
},
profileImage: {
data: Buffer,
contentType: String
},
isAdmin: {
type: Boolean,
'default': false
},
bio: {
type: String,
'default': ''
}
});
mongoose.model('User', userSchema);
Event Schema:
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var userSchema = mongoose.model('User').schema;
var eventSchema = new Schema({
title: {
type: String,
required: true
},
date: {
type: Date,
'default': Date.now
},
place: {
type: String,
required: true
},
distance: {
type: String,
'default': '-1'
},
eventType: {
type: Number,
'default': 2 /* 0 = Asfalto, 1 = Trail, 2 = Otros */
},
attenders: [userSchema],
comment: {
type: String,
'default': ''
}
});
mongoose.model('Event', eventSchema);
And then I've defined a controller to manage POST requests over a certain endpoint. On my controller I only checks 'title' and 'place' fields on Event requests, like this:
if (!req.body.title) {
response.sendJSON(res, 400, null, {"message": "title param is needed"});
return;
}
if (!req.body.place) {
response.sendJSON(res, 400, null, {"message": "place param is needed"});
return;
}
Events
.create({
title: req.body.title,
place: req.body.place
}, function(err, event) {
if (err) {
response.sendJSON(res, 400, null, err);
} else {
response.sendJSON(res, 201, event, null);
}
});
};
So I'm sending a request, using a 'title':'Example title', 'place':'Spain' as body params.
The first request creates new event successfully, but the nexts requests show me this error:
"status": 400,
"error": {
"code": 11000,
"index": 0,
"errmsg": "E11000 duplicate key error index: som-passatge.events.$attenders.email_1 dup key: { : null }",
"op": {
"title": "Título de evento 22",
"place": "Valencia",
"_id": "56cc905d496141587dc47caf",
"comment": "",
"attenders": [],
"eventType": 2,
"distance": "-1",
"date": "2016-02-23T17:01:17.650Z",
"__v": 0
}
},
"response": null
I don't know what I'm doing wrong...