Creating instance with an association in Sequelize - node.js

Using Sequelize, I've created two models: User and Login.
Users can have more than one Login, but a login must have exactly one user, which means a Login cannot be saved without a User ID.
How do I .create a Login with a User association all in one swoop?
Current Code (Doesn't Work)
// Set up the models
var User = sequelize.define('User', {});
var Login = sequelize.define('Login', {});
Login.belongsTo(User, {
onDelete: 'cascade',
foreignKey: {
field: 'userId',
allowNull: false,
}
});
// Create the instances
var user = User.create().then(function() {
// THIS IS WHERE I WOULD LIKE TO SET THE ASSOCIATION
var login = Login.create({
userId: user.get('id')
});
)};
The above results in SequelizeValidationError: notNull Violation: UserId cannot be null

Assuming you have the right association between users and login,
you can just create a user including a login:
User.create({
name: "name",
Login: {...}
},{
include: Login
})
you can find more information here:
https://sequelize.org/docs/v6/advanced-association-concepts/creating-with-associations/

First of all you need to setup the relations in both ways, like this:
// Set up the models
var User = sequelize.define('User', {});
var Login = sequelize.define('Login', {});
// Set the correct associations
User.hasMany(Login, {})
Login.belongsTo(User, {});
Then, you need to properly get the instances returned by the promises:
// Create the instances
User.create({}).then(function(newUser) {
// now you can use newUser acessors to create the login
return newUser.createLogin({});
).then(function(newLogin){
// newLogin
}).catch(function(error){
// error
});

In your .then, the callback receives the model instance created by the previous call. You need to specify the argument inside the callback function.
var user = User.create().then(function(user) {
// THIS IS WHERE I WOULD LIKE TO SET THE ASSOCIATION
var login = Login.create({
userId: user.get('id')
});
return login
}).then(function(login) {
// all creation are complete. do something.
});
Also something important I would like to point out is your missing var statements! Those are important but not related to this question. See Declaring variables without var keyword

An Update to #Arwed Mett's answer
//Create Association Alias or just setting association alias by using 'as' keyword will also work
Login.User = Login.belongsTo(User);
User.create({
name: "name",
Login: {...}
}, {
include: [{
association: Login.User
}]
});
Refrence link - http://docs.sequelizejs.com/manual/tutorial/associations.html#creating-with-associations

For those like me who were trying to create an instance of a model including another instance, like:
var login1 = await Login.create(...);
var user1 = await User.create({
Login: login1
}, {
include: Login
});
You can't because this method is used to embed an instance (Login) which is not already existing and that will be created at the parent instance (User) creation level.
So, if you want to embed an already existing Login in the newly created User, do instead:
var login1 = await Login.create(...);
var user1 = await User.create({
loginId: login1.get('id')
}, {});

You have association between User an Login with constraint allowNull at false. You must create Login before User or set allowNull at true in model and the table to DB (LoginId Null constraint)
var User = sequelize.define('User', {});
var Login = sequelize.define('Login', {});
Login.belongsTo(User, {
onDelete: 'cascade',
foreignKey: {
field: 'userId',
allowNull: false,
}
});
Solution
Login.create({
username: "username",
User: {...}
},{
include: User
})

I have the same issue recently!
I have a typo mistake with the foreignKey config. Use field instead of name caused the issue.
The change below will fix it.
{
foreignKey: {
name: 'userId',
allowNull: false,
}
}

As an extra you could also nest your creation to be even more effective and concise.
// Set up the models
var User = sequelize.define('User', {});
var Login = sequelize.define('Login', {});
...
User.create({
name: "name",
Login:
{
users: {..i.e several users if a user belongs to another user..}
}
},{
include:{
model: Login,
include: User //nested model.Create
}
})
as seen here: https://github.com/sequelize/sequelize/issues/7252

Related

getting the follwerList from the through table (sequelize)

Currently making a sns project.
Have a user model and made a N:M association which tells you who is following who.
So there is a connected models between 'user' and 'user'.
This is how my code looks like
static associate(db) {
db.User.hasMany(db.Post);
db.User.belongsToMany(db.User, {
foreignKey: 'followingId',
as: 'Followers',
through: 'Follow',
});
db.User.belongsToMany(db.User, {
foreignKey: 'followerId',
as: 'Followings',
through: 'Follow',
});
}
and I'm trying to show how many followers and following the user has at the profile page.
So what I did is when the /main pass
const User = require('../models/user');
router.use((req, res, next) => {
res.locals.followingList = User.findAll({
where : {followerId : req.user}
});
next();
});
Having a problem accessing the data from through table.
Having a problem accessing the data from through table.
If we want to get the user along with followings and followers then you need to use findOne (or findByPk if req.user is a primary key value) because we want a single user and just include both associations in the query though I don't recommend to include more than one M:N associations to the same query:
res.locals.followingList = await User.findOne({
where : {id : req.user},
include: [{
model: User,
as: 'Followers'
}, {
model: User,
as: 'Followings'
}]
});

how to stop users from viewing and updating another user's data in node.js?

I am storing a parking detail with a merchant id in the mongoose schema since a parking belongs to a certain merchant user and it cannot be empty or null.
Here is the model:
const parkingSchema = new mongoose.Schema({
merchantId: {
type: mongoose.Schema.Types.ObjectId,
required: true,
ref: "Merchant",
},
//other details
})
merchant model is something like this:
const merchantSchema = new mongoose.Schema({
merchantId: {
type: mongoose.Schema.Types.ObjectId,
ref: "Auth",
},
//other details
})
And finally the auth schema:
const authSchema = new mongoose.Schema({
accountType: {
type: String,
required: true,
trim: true,
default: "user",
enum: ["merchant", "user", "provider"],
},
//other details
})
If the original user wishes it, I simply want to update the parking data; otherwise, I want to throw an error.
I am using jsonwebtoken to authenticate users.
Here is the query to update the data:
exports.updateParking = async (req, res) => {
try {
const { parkingName, price, address, name, phoneNumber, about } = req.body;
const { parkingImage } = req.files;
const check_exist = await Auth.findById(req.data.id);
if (!check_exist) return res.status(404).json({ error: "User not found" });
console.log(req.data.id);
const updateData = await Parking.findByIdAndUpdate(
{ _id: req.params.id, merchantId: req.data.id }, // I think here is the problem
{
$set: {
parkingName,
price,
address,
...
},
}
);
return res.status(200).json({
success: true,
msg: "Parking has updated successfully",
});
} catch (error) {
return error.message;
}
};
However, the issue is that other users can now update another user's data which I want to stop
below is the query of middleware:
routing.patch("/parking/update/:id", middleware.authenticateToken, merchant.updateParking)
You should be showing each user only their parkings that they have created or belong to them.
const myParkings = async (req, res) => {
// always await code in try/catch block
const merchants = await Parkings.find({ user: req.user._id })
.. then populate the fields that you want to show
res.status(200).json({
success: true,
bookings,
});
};
you have to set this req.user._id when user logins. You could create a session.
I think what you're looking for is something like CASL Mongoose (or a similar package), and more specifically, the "conditions" section of the CASL docs.
What you're dealing with here is the distinction between 2 concepts:
AuthN (authentication) - determines who someone is and whether they are "authenticated" to make an API request
AuthZ (authorization) - determines what the authenticated user is allowed to do
In your app, middleware.authenticateToken is responsible for the AuthN piece of the equation. It makes sure that only users that have created an account are able to make requests to your API routes.
What you still need to solve for is the AuthZ piece, which can be done in a bunch of different ways, but one popular one is to use CASL, which is a Node AuthZ library that allows you to utilize your ORM's native query syntax to limit actions based on the authenticated (AuthN) user's attributes.
In other words, you can do something like, "Only allow user with ID 1 to update Parking entities that he/she owns". Below is generally what you're looking for (not tested for your use case, but the general idea is here):
const casl = require('#casl/ability');
// Define what a `Auth` (user) can do based on their database ID
function defineMerchantAbilities(merchantUser) {
const abilities = casl.defineAbility((allow, deny) => {
// Allow merchant to update a parking record that they own
allow('update', 'Parking', { merchantId: merchantUser.id })
})
return abilities
}
exports.updateParking = async (req, res) => {
const userId = req.data.id
const parkingId = req.params.id
// Find your merchant user in DB (see my comments at end of post)
const merchantUser = await Auth.findById(userId)
// Find your parking record
const parking = await Parking.findById(parkingId)
// Pass user to your ability function
const ability = defineMerchantAbilities(merchantUser)
// This will throw an error if a user who does not own this Parking record
// tries to update it
casl.ForbiddenError
.from(ability)
.throwUnlessCan('update', casl.subject('Parking', parking))
// If you make it here, you know this user is authorized to make the change
Parking.findByIdAndUpdate( ...your code here )
}
Additional comments/notes:
I would recommend removing your try/catch handler and using an Express default error handler as it will reduce the boilerplate you have to write for each route.
I would also recommend writing a middleware that finds a user by ID in the database and attaches it to a custom property called req.user so you always have req.user available to you in your authenticated routes.

How do I reference an association when creating a row in sequelize without assuming the foreign key column name?

I have the following code:
#!/usr/bin/env node
'use strict';
var Sequelize = require('sequelize');
var sequelize = new Sequelize('sqlite:file.sqlite');
var User = sequelize.define('User', { email: Sequelize.STRING});
var Thing = sequelize.define('Thing', { name: Sequelize.STRING});
Thing.belongsTo(User);
sequelize.sync({force: true}).then(function () {
return User.create({email: 'asdf#example.org'});
}).then(function (user) {
return Thing.create({
name: 'A thing',
User: user
}, {
include: [User]
});
}).then(function (thing) {
return Thing.findOne({where: {id: thing.id}, include: [User]});
}).then(function (thing) {
console.log(JSON.stringify(thing));
});
I get the following output:
ohnobinki#gibby ~/public_html/turbocase1 $ ./sqltest.js
Executing (default): INSERT INTO `Users` (`id`,`email`,`updatedAt`,`createdAt`) VALUES (NULL,'asdf#example.org','2015-12-03 06:11:36.904 +00:00','2015-12-03 06:11:36.904 +00:00');
Executing (default): INSERT INTO `Users` (`id`,`email`,`createdAt`,`updatedAt`) VALUES (1,'asdf#example.org','2015-12-03 06:11:36.904 +00:00','2015-12-03 06:11:37.022 +00:00');
Unhandled rejection SequelizeUniqueConstraintError: Validation error
at Query.formatError (/home/ohnobinki/public_html/turbocase1/node_modules/sequelize/lib/dialects/sqlite/query.js:231:14)
at Statement.<anonymous> (/home/ohnobinki/public_html/turbocase1/node_modules/sequelize/lib/dialects/sqlite/query.js:47:29)
at Statement.replacement (/home/ohnobinki/public_html/turbocase1/node_modules/sqlite3/lib/trace.js:20:31)
It seems that specifying {include: [User]} instructs Sequelize to create a new User instance matching the contents of user. That is not my goal. In fact, I find it hard to believe that such behaviour would ever be useful—I at least have no use for it. I want to be able to have a long-living User record in the database and at arbitrary times create new Things which refer to the User. In my shown example, I wait for the User to be created, but in actual code it would likely have been freshly loaded through User.findOne().
I have seen other questions and answers say that I have to explicitly specify the implicitly-created UserId column in my Thing.create() call. When Sequelize provides an API like Thing.belongsTo(User), I shouldn’t have to be aware of the fact that a Thing.UserId field is created. So what is the clean API-respecting way of creating a new Thing which refers to a particular User without having to guess the name of the UserId field? When I load a Thing and specify {include: [User]}, I access the loaded user through the thing.User property. I don’t think I’m supposed to know about or try to access a thing.UserId field. In my Thing.belongsTo(User) call, I never specify UserId, I just treat that like an implementation detail I shouldn’t care about. How can I continue to avoid caring about that implementation detail when creating a Thing?
The Thing.create() call that works but looks wrong to me:
Thing.create({
name: 'A thing',
UserId: user.id
});
Option 1 - risks DB inconsistency
Sequelize dynamically generates methods for setting associations on instances, e.g. thing.setUser(user);. In your use case:
sequelize.sync({force: true})
.then(function () {
return Promise.all([
User.create({email: 'asdf#example.org'}),
Thing.create({name: 'A thing'})
]);
})
.spread(function(user, thing) {
return thing.setUser(user);
})
.then(function(thing) {
console.log(JSON.stringify(thing));
});
Option 2 - does not work/buggy
It isn't documented, but from a code dive I think the following should work. It doesn't but that seems to be because of a couple of bugs:
// ...
.then(function () {
return models.User.create({email: 'asdf#example.org'});
})
.then(function(user) {
// Fails with SequelizeUniqueConstraintError - the User instance inherits isNewRecord from the Thing instance, but it has already been saved
return models.Thing.create({
name: 'thingthing',
User: user
}, {
include: [{
model: models.User
}],
fields: ['name'] // seems nec to specify all non-included fields because of line 277 in instance.js - another bug?
});
})
Replacing models.User.create with models.User.build doesn't work because the built but not saved instance's primary key is null. Instance#_setInclude ignores the instance if its primary key is null.
Option 3
Wrapping the Thing's create in a transaction prevents an inconsistent state.
sq.sync({ force: true })
.then(models.User.create.bind(models.User, { email: 'asdf#example.org' }))
.then(function(user) {
return sq.transaction(function(tr) {
return models.Thing.create({name: 'A thing'})
.then(function(thing) { return thing.setUser(user); });
});
})
.then(print_result.bind(null, 'Thing with User...'))
.catch(swallow_rejected_promise.bind(null, 'main promise chain'))
.finally(function() {
return sq.close();
});
I have uploaded a script demo'ing option 2 and option 3 here
Tested on sequelize#6.5.1 sqlite3#5.0.2 I can use User.associations.Comments.foreignKey as in:
const Comment = sequelize.define('Comment', {
body: { type: DataTypes.STRING },
});
const User = sequelize.define('User', {
name: { type: DataTypes.STRING },
});
User.hasMany(Comment)
Comment.belongsTo(User)
console.dir(User);
await sequelize.sync({force: true});
const u0 = await User.create({name: 'u0'})
const u1 = await User.create({name: 'u1'})
await Comment.create({body: 'u0c0', [User.associations.Comments.foreignKey]: u0.id});
The association is also returned during creation, so you could also:
const Comments = User.hasMany(Comment)
await Comment.create({body: 'u0c0', [Comments.foreignKey]: u0.id});
and on many-to-many through tables you get foreignKey and otherKey for the second foreign key.
User.associations.Comments.foreignKey contains the foreignKey UserId.
Or analogously with aliases:
User.hasMany(Post, {as: 'authoredPosts', foreignKey: 'authorId'});
Post.belongsTo(User, {as: 'author', foreignKey: 'authorId'});
User.hasMany(Post, {as: 'reviewedPosts', foreignKey: 'reviewerId'});
Post.belongsTo(User, {as: 'reviewer', foreignKey: 'reviewerId'});
await sequelize.sync({force: true});
// Create data.
const users = await User.bulkCreate([
{name: 'user0'},
{name: 'user1'},
])
const posts = await Post.bulkCreate([
{body: 'body00', authorId: users[0].id, reviewerId: users[0].id},
{body: 'body01', [User.associations.authoredPosts.foreignKey]: users[0].id,
[User.associations.reviewedPosts.foreignKey]: users[1].id},
])
But that syntax is so long that I'm tempted to just hardcode the keys everywhere.

NodeJS Sequelize - Multiple Ownership

In my application I have Users and Contacts. Users make ContactRequests to other Users. The ContactRequest has a requester and requestee field. I don't know how to go about this. I don't want a Users, ContactRequests, and ContactRequestsUsers table (as sequelize would if I defined a hasMany or hasOne on both Users and ContactRequests).
So let me start by defining some models-
var User = sequelize.define('User', {
id: {type: seq.INTEGER, primaryKey: true, autoIncrement: true},
username: {
type: sequelize.STRING(32)
}
});
var ContactRequest = sequelize.define('ContactRequest', {
id: {type: seq.INTEGER, primaryKey: true, autoIncrement: true}
});
Not sure what associations I should make. It seems like I should be able to do this several ways (in theory) but I can't get any of the ways I've tried to work.
// User Has Many?
User.hasMany(ContactRequest, {as: 'Requester', foreignKey: 'requester'});
User.hasMany(ContactRequest, {as: 'Requestee', foreignKey: 'requestee'});
// ContactRequest Belongs to?
ContactRequest.belongsTo(User, {});
// Experience Has One?
ContactRequest.hasOne(User, {as: 'Requester'});
ContactRequest.hasOne(User, {as: 'Requestee'});
With each variation I've tried different methods to checking to see if userA has made a ContactRequest to userB.
// Checking if a user has a contact request?
var userA = 'someuser1';
var userB = 'someuser2';
ContactRequest.find({where: {'Requester.username': userA, 'Requestee.username': userB}, include: [{model: User, as: 'Requester'}, {model: User, as: 'Requestee'}]}).success(function(cr) {
console.log(cr);
});
// Maybe by the user?
User.find({where: {username: userA}}).success(function(someuser) {
someuser.getRequestee({where: {'Requestee.username': userB}, include: [{model: User, as: 'Requestee'}]}).success(function(cr) {
console.log(cr);
});
});
Not sure where I'm going wrong but I can't get this to work. Any help or foresight would be appreciated.

Try to create HABTM connection Sequelize.js

I'm trying to create a HABTM relationship with Sequelize but I can't get it done.... I still receive an error message:
return (tableName1.toLowerCase() < tableName2.toLowerCase()) ? (tableName1
^
TypeError: Cannot call method 'toLowerCase' of undefined
I have a User model, a Book model and an UserBooks model. And ofcourse my database contains a "users" table, "user_books" table and "books" table.
UserBooks model:
module.exports = function(schema, DataTypes) {
var UserBooks = schema.define('UserBooks', {
}, {
tableName: 'user_books', // this will define the table's name
timestamps: false // this will deactivate the timestamp columns
});
UserBooks.sync();
return UserBooks;
};
User model:
module.exports = function(schema, DataTypes) {
var User = schema.define('User', {
keywords: DataTypes.STRING
}, {
tableName: 'users', // this will define the table's name
timestamps: false ,// this will deactivate the timestamp columns
syncOnAssociation:false
});
User.hasMany(Book, { foreignKey: 'user_id', through: UserBooks });
User.sync();
return User;
};
Book model:
module.exports = function(schema, DataTypes) {
var Book = schema.define('Book', {
keywords: DataTypes.STRING
}, {
tableName: 'books', // this will define the table's name
timestamps: false ,// this will deactivate the timestamp columns
syncOnAssociation:false
});
Book.hasMany(User, { foreignKey: 'book_id', through: UserBooks });
Book.sync();
return Book;
};
In your User model you are trying to create an association with a model that is not defined in that scope. In User.js, you only have access to User, not Book or UserBooks which are undefined. Thats whats causing your error.
You can either create associations in the place where you import all your models into your app, or in the models file by importing the models you want to associate with (bevare of circular imports). Your user model could be changed to:
module.exports = function(schema, DataTypes) {
var Book = schema.import(__dirname + '/book');
var UserBooks = schema.import(__dirname + '/userbooks');
var User = schema.define('User', {
keywords: DataTypes.STRING
}, {
tableName: 'users', // this will define the table's name
timestamps: false ,// this will deactivate the timestamp columns
syncOnAssociation:false
});
User.hasMany(Book, { foreignKey: 'user_id', through: UserBooks });
Book.hasMany(User, { foreignKey: 'book_id', through: UserBooks });
return User;
};
For another example of how to do it, see http://sequelizejs.com/articles/express#minimal-express-app
Also, I've removed the call to User.sync from your code. Sync is an async call, while import is sync. This means that your are defining your model, starting to sync it to the DB, and then returning it, before you know that it has finished syncing. This means you could potentially be trying to work create instances with it before the table has been created. Instead, you should use sequelize.sync to sync all your models at once, and attach a callback to wait for the sync to finish (see the link I posted for a code example)

Resources