I am using typescript with Sequelize ORM, I have a User model which has some type definitions like so:
export interface UserAttributes {
id: number
name: string
username: string
email: string
...
}
interface UserCreationAttributes
extends Optional<
UserAttributes,
| 'id'
...
> {}
export class UserModel
extends Model<UserAttributes, UserCreationAttributes>
implements UserAttributes {
id!: number
name!: string
username!: string
email!: string
...
}
And here is the user model itself:
UserModel.init(
{
id: {
type: DataTypes.INTEGER.UNSIGNED,
autoIncrement: true,
primaryKey: true,
allowNull: false,
},
name: {
type: DataTypes.STRING,
allowNull: false,
},
username: {
type: DataTypes.STRING,
allowNull: false,
unique: true,
},
...
}
)
When I use the User model like so:
async function createUser({...params}) {
const user = await User.create({...params})
const {id, name, email, } = user
console.log(name, email) // returns `undefined` `undefined`
return user // id is null when returned but other values are returned
}
Also user.toJSON() returns the values, but id comes back as null. How do I rectify this issue? Also, what could be the cause of this? Thank you very much.
Related
Environment
Sequelize version: 6.6.5
Node.js version: 14.17.6
If TypeScript related: 4.4.3
Dialect sqlite
When using typescript and getters/setters, if the column DataType does not match the interface/attribute type, typescript throws an error.
Example:
interface IUserAttributes {
id: number;
registeredAtIp: string;
}
interface IUserCreationAttributes extends Optional<IUserAttributes, 'id'> {}
class User extends Model<IUserAttributes, IUserCreationAttributes> implements IUserAttributes {
public id!: number;
public registeredAtIp!: string;
}
User.init(
{
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true,
unique: true,
},
registeredAtIp: {
type: DataTypes.INTEGER,
allowNull: false,
get(): string {
return int2Ip(this.getDataValue("registeredAtIp"));
// ^
// Argument of type 'string' is not assignable to parameter of type 'number'.ts(2345)
// this: User
},
set(newIp: string): void {
this.setDataValue("registeredAtIp", ip2Int(newIp));
// ^
// Argument of type 'number' is not assignable to parameter of type 'string'.ts(2345)
// (alias) ip2Int(ip: string): number
// import ip2Int
},
},
},
{ sequelize, tableName: "users" }
);
Shouldn't getDataValue return number as the column DataType is INTEGER and not string?
Setting the interface type to number fixes the error but I can't create a User which has the property as a string.
I want to know how to stop the error, aka this.getDataValue('registeredAtIp') should be of type number.
NOTE: I already know I can do as unknown as number but I shouldn't have to do such a thing.
Asked on github: https://github.com/sequelize/sequelize/issues/13522
I'm using nodejs and typescript to build a user model. In my mongoose model I'm trying to add a match property to the email field of the Schema but I keep getting this typescript error.:
Argument of type '{ firstName: { type: any; required: [true, string]; }; lastName: { type: any; required: [true, string]; }; email: { type: any; required: [true, string]; unique: true; match: (string | RegExp)[]; }; password: { ...; }; role: { ...; }; resetPasswordToken: any; resetPasswordExpire: DateConstructor; }' is not assignable to parameter of type 'SchemaDefinition'.
Property 'email' is incompatible with index signature.
Type '{ type: any; required: [true, string]; unique: true; match: (string | RegExp)[]; }' is not assignable to type 'string | Function | Function[] | Schema<Document<any>, Model<Document<any>>> | SchemaDefinition | SchemaTypeOptions<any> | Schema<Document<any>, Model<Document<any>>>[] | SchemaTypeOptions<...>[] | SchemaDefinition[]'.
Type '{ type: any; required: [true, string]; unique: true; match: (string | RegExp)[]; }' is not assignable to type 'string'.
This is the model file:
user.ts
import mongoose, { Model, Schema } from 'mongoose';
import crypto from 'crypto';
import bcrypt from 'bcryptjs';
import jwt from 'jsonwebtoken';
import User from '../interfaces/User';
const userSchema: Schema = new mongoose.Schema({
firstName: {
type: String,
required: [true, 'Please enter a first name']
},
lastName: {
type: String,
required: [true, 'Please enter a last name']
},
email: {
type: String,
required: [true, 'Please enter an email'],
unique: true,
match: [
/^\w+([\.-]?\w+)*#\w+([\.-]?\w+)*(\.\w{2,3})+$/,
'Please add a valid email'
]
},
password: {
type: String,
required: [true, 'Please add a password'],
minlength: 6,
select: false
},
role: {
type: String,
enum: ['admin', 'customer'],
default: 'customer'
},
resetPasswordToken: String,
resetPasswordExpire: Date
}, {
timestamps: true
});
userSchema.pre<User>('save', async function(next) {
if (!this.isModified('password')) {
next();
}
const salt = await bcrypt.genSalt(10);
this.password = await bcrypt.hash(this.password, salt);
});
userSchema.methods.getSignedJwtToken = function(next: any) {
// #ts-ignore
return jwt.sign({id: this._id}, process.env.JWT_SECRET, {expiresIn: process.env.JWT_EXPIRES});
};
userSchema.methods.matchPassword = async function(enteredPassword: string) {
// #ts-ignore
return await bcrypt.compare(enteredPassword, this.password);
};
userSchema.methods.getResetPasswordToken = function() {
const resetToken = crypto.randomBytes(20).toString('hex');
// #ts-ignore
this.resetPasswordToken = crypto.createHash('sha256').update(resetToken).digest('hex');
// #ts-ignore
this.resetPasswordExpire = Date.now() + 10 * 60 * 1000;
return resetToken;
}
export default mongoose.model('User', userSchema);
And this is the interface I'm using:
User.ts
import { Document } from 'mongoose';
export default interface User extends Document {
firstName: string,
lastName: string,
email: string,
password: string,
role: string,
resetPasswordToken: string,
resetPasswordExpire: Date
}
I noticed the error comes when I add the match property to the email object.
In getResetPasswordToken add this.save() before return resetToken
For mongoose schemas, you should use capitalized version of String,which is an actual value type in javascript String Object, and should not be confused with the typescript primitive string type.
In a Next.js context, I'm trying to insert a data in a table using this code snippet :
const user = new User();
user.firstname = "John";
user.lastname = "Appleseed";
user.email = "john.appleseed#apple.com";
await user.create()
But when I inspect the log here's what I see
Basically, the data I put in the request are not written on the DB, but in a certain way Sequelize receive it (as shown in _previousData). But I don't know why there are moved from dataValues to theses _previousData
Here are the model I'm using
The core model :
export default class User {
id!: bigint
hash!: string
firstname?: string
lastname?: string
email?: string
isValidated?: boolean = false
async create(): Promise<void> {
try {
await UserEntity.create({
accountValidated: false,
firstname: this.firstname,
lastname: this.lastname,
email: this.email
});
} catch (error) {
console.error('User.create', error.message)
throw error
}
}
}
Here's the entity related to Sequelize and my PG
interface UserAttributes {
id?: bigint
hash?: string
firstname?: string
lastname?: string
email?: string
accountValidated: boolean
}
interface UserCreationAttributes extends Optional<UserAttributes, "id"> {}
export default class UserEntity extends Model<UserAttributes, UserCreationAttributes> implements UserAttributes {
public id!: bigint
public hash!: string
public firstname?: string
public lastname?: string
public email?: string
public accountValidated!: boolean
public readonly createdAt!: Date
public readonly updatedAt!: Date
public readonly accounts?: AccountEntity[]; // Note this is optional since it's only populated when explicitly requested in code
public static associations: {
accounts: Association<UserEntity, AccountEntity>;
};
}
UserEntity.init({
id: {
type: DataTypes.BIGINT,
primaryKey: true,
allowNull: false,
autoIncrement: true,
unique: true
},
hash: {
type: DataTypes.STRING,
allowNull: false,
unique: true
},
firstname: {
type: DataTypes.STRING,
allowNull: true
},
lastname: {
type: DataTypes.STRING,
allowNull: true
},
email: {
type: DataTypes.STRING,
allowNull: true,
unique: true,
validate: {
isEmail: {
msg: "This email is malformed"
}
}
},
accountValidated: {
type: DataTypes.BOOLEAN,
defaultValue: false
},
}, {
sequelize, // We need to pass the connection instance
modelName: 'User',
underscored: true,
tableName: 'blk_user',
timestamps: true
})
UserEntity.beforeValidate(user => {
user.hash = `usr_${v4()}`
})
UserEntity.beforeCreate(instance => console.log('beforeCreate', instance))
What I've tried
[x] Comment hooks
[x] Comment associations
EDIT 11/16 6:39 CET
Seems that it is linked to my way of declaring my models TS interfaces and models. I'll dig into it
I am using node sequelize library to insert data in postgress db
this is my user model in Users.ts file
export class User extends Sequelize.Model {
public id!: number;
public name: string;
public email: string;
public googleId: string;
public password: string;
// public preferredName!: string | null; // for nullable fields
// timestamps!
public readonly createdAt!: Date;
public readonly updatedAt!: Date;
async comparePassword(candidatePassword: string, callback: Function) {
try {
const isMatch = await bcrypt.compare(candidatePassword, this.password);
callback(undefined, isMatch);
} catch (err) {
callback(err);
}
}
}
User.init(
{
id: {
type: Sequelize.DataTypes.INTEGER.UNSIGNED,
autoIncrement: true,
primaryKey: true,
},
name: {
type: Sequelize.DataTypes.STRING,
allowNull: false,
},
email: {
type: Sequelize.DataTypes.STRING(128),
allowNull: false,
unique: true,
},
googleId: {
type: Sequelize.DataTypes.STRING(128),
allowNull: true,
},
password: {
type: Sequelize.DataTypes.STRING(300),
allowNull: true,
},
token: {
type: Sequelize.DataTypes.STRING(300),
allowNull: true,
},
},
{
tableName: 'users',
sequelize: sequelize, // this bit is important
}
);
When i try to create a new record in the db using
await User.create({name, email, password, address});
it always return me error with invalid value with whatever input i give
Error: Invalid value { name: 'd' } at Object.escape
(/Users/eali/learn/akasa-server/node_modules/sequelize/lib/sql-string.js:65:11)
at PostgresQueryGenerator.escape
(/Users/eali/learn/akasa-server/node_modules/sequelize/lib/dialects/abstract/query-generator.js:964:22)
at PostgresQueryGenerator._whereParseSingleValueObject
(/Users/eali/learn/akasa-server/node_modules/sequelize/lib/dialects/abstract/query-generator.js:2431:41)
at PostgresQueryGenerator.whereItemQuery
(/Users/eali/learn/akasa-server/node_modules/sequelize/lib/dialects/abstract/query-generator.js:2149:19)
at Utils.getComplexKeys.map.prop
(/Users/eali/learn/akasa-server/node_modules/sequelize/lib/dialects/abstract/query-generator.js:2206:21)
at Array.map () at PostgresQueryGenerator._whereBind
(/Users/eali/learn/akasa-server/node_modules/sequelize/lib/dialects/abstract/query-generator.js:2204:43)
at PostgresQueryGenerator.whereItemQuery
(/Users/eali/learn/akasa-server/node_modules/sequelize/lib/dialects/abstract/query-generator.js:2139:19)
at Utils.getComplexKeys.forEach.prop
(/Users/eali/learn/akasa-server/node_modules/sequelize/lib/dialects/abstract/query-generator.js:2048:25)
at Array.forEach () at
PostgresQueryGenerator.whereItemsQuery
(/Users/eali/learn/akasa-server/node_modules/sequelize/lib/dialects/abstract/query-generator.js:2046:35)
at PostgresQueryGenerator.getWhereConditions
(/Users/eali/learn/akasa-server/node_modules/sequelize/lib/dialects/abstract/query-generator.js:2458:19)
at PostgresQueryGenerator.selectQuery
(/Users/eali/learn/akasa-server/node_modules/sequelize/lib/dialects/abstract/query-generator.js:1279:28)
at QueryInterface.select
(/Users/eali/learn/akasa-server/node_modules/sequelize/lib/query-interface.js:1120:27)
at Promise.try.then.then.then
(/Users/eali/learn/akasa-server/node_modules/sequelize/lib/model.js:1748:34)
at tryCatcher
(/Users/eali/learn/akasa-server/node_modules/bluebird/js/release/util.js:16:23)
at Promise._settlePromiseFromHandler
(/Users/eali/learn/akasa-server/node_modules/bluebird/js/release/promise.js:512:31)
at Promise._settlePromise
(/Users/eali/learn/akasa-server/node_modules/bluebird/js/release/promise.js:569:18)
at Promise._settlePromise0
(/Users/eali/learn/akasa-server/node_modules/bluebird/js/release/promise.js:614:10)
at Promise._settlePromises
(/Users/eali/learn/akasa-server/node_modules/bluebird/js/release/promise.js:694:18)
at _drainQueueStep
(/Users/eali/learn/akasa-server/node_modules/bluebird/js/release/async.js:138:12)
at _drainQueue
(/Users/eali/learn/akasa-server/node_modules/bluebird/js/release/async.js:131:9)
My first guess would be that you forgot to add values to your property keys:
await User.create({name, email, password, address});
should be:
await User.create({name: 'john wayne', email: 'lol#example.com', password: '1234', address: 'Someplace 1'});
I'm having trouble populating my user model with typescript. I have a property of the user's scheme which is the reference to his profile. To implement mongoose with typescript I had to define a tpo / interface in addition to the schema. My problem is that I do not know what type I have to assign to the profile property. Because if I run the query without populating it, it will be an "ObjectId", but if I populate it will contain the whole profile document.
export type UserModel = mongoose.Document & {
name: string;
username: string;
email: string;
password: string;
profile: Schema.Types.ObjectId; // If I leave this property with this type, in case I populate a typescript query it would give me an error.
};
export const UserSchema = new Schema({
name: {
type: String,
required: true
},
username: {
type: String,
required: true,
unique: true
},
email: {
type: String,
unique: true,
required: true
},
password: {
type: String,
required: true
},
profile: {
type: Schema.Types.ObjectId,
ref: "Profile"
}
});
export default model<UserModel>("User", UserSchema);
My problem is that I do not know what type I have to assign to the
profile property.
You are going to need to define 2 types/interfaces.
export default model<UserModel>("User", UserSchema);
This export will have to use a different interface in which you have the populated "Profile" document.
For the schema itself you can leave it as an objectID type.
Try to set the populate pointing to the model, this should do the trick
User.find({}).populate([{"path":"profile", "model":"Profile" }])