Can anyone help me to know how to populate a query in typeorm. Like I have this entity
#Entity('users')
export class User extends BaseEntity {
#Column()
userName : string;
#Column()
email : string;
#OneToMany(() => UserFollowing, (userFollowing) => userFollowing.followee)
followers: User[];
#OneToMany(() => UserFollowing, (userFollowing) => userFollowing.follower)
followees: User[];
}
#Entity('user_followings')
export class UserFollowing extends FakeBaseEntity {
#JoinColumn({ name : 'follower_id' })
#ManyToOne(() => User, user => user.followees)
follower : User;
#JoinColumn({ name : 'followee_id' })
#ManyToOne(() => User, user => user.followers)
followee : User;
}
Now to get all the followers and followees of a particular userid
Here are my two approaches : both giving same output
const info = await this.userRepo
.createQueryBuilder('userFollowing')
.select()
.leftJoinAndSelect('userFollowing.followers','followers')
.leftJoinAndSelect('userFollowing.followees', 'followees')
.where('userFollowing.id = :userid', { userid })
.getMany()
return info;
------------------------------------------------------------
const info = this.userRepo.find({
where: {
id : userid,
},
relations: ["followers", "followees"],
})
return info;
output I am recieving : and I want all the info about followers and followees
{
"id": "e8651d4f-3c7b-4f5a-8205-7370b107d98c",
"userName": "something",
"email" : "something#gmail.com",
"followers": [
{
"id": "f54b8574-10ea-4133-85bd-5f8fcda4eeb9",
"createdAt": "2021-08-12T03:58:39.198Z",
"updatedAt": "2021-08-12T03:58:39.198Z"
}
],
"followees": [
{
"id": "eb2cb728-a1c0-4bea-9230-712827c714c7",
"createdAt": "2021-08-12T03:59:32.260Z",
"updatedAt": "2021-08-12T03:59:32.260Z"
}
]
}
If I have understood your question correctly, then what you are looking for is to get the data of followers and followees as well.
You can easily achieve this with the find function.
This is how your query should look:
const info = this.userRepo.find({
where: {
id : userid,
},
relations: ["followers", "followees", "followers.follower", "followees.followee"],
})
As you can see, I have passed two more string values in the relations.
Using this, it will load the sub-relations as well (stated in the TypeORM Find Options).
You can achieve the same using query builder as well by adding more .leftJoinAndSelect method chain.
External Links:
TypeORM Find Options
Related
I have a query:
createNotification: async (_, args, {req, res}) => {
const followedBy = await prisma.user.updateMany({
where: {
following: {
some: {
id: req.userId
},
},
},
data: {
notifications: {
create: {
message: args.message,
watched: false,
},
},
},
})
And User and Notification models:
model User {
id Int #id #default(autoincrement())
email String #unique
name String
user_name String #unique
password String
movies Movie[]
notifications Notification[]
followedBy User[] #relation("UserFollows", references: [id])
following User[] #relation("UserFollows", references: [id])
}
model Notification {
id Int #id #default(autoincrement())
link String?
movie_id Int?
message String
icon String?
thumbnail String?
user User #relation(fields: [userId], references: [id])
userId Int
watched Boolean
}
When I run my query I get an error:
Unknown arg `notifications` in data.notifications for type UserUpdateManyMutationInput. Did you mean `email`? Available args:
type UserUpdateManyMutationInput {
email?: String | StringFieldUpdateOperationsInput
name?: String | StringFieldUpdateOperationsInput
user_name?: String | StringFieldUpdateOperationsInput
password?: String | StringFieldUpdateOperationsInput
}
The strange thing is that this works:
const followedBy = await prisma.user.findUnique({
where: {id: req.userId},
include: {
followedBy: true,
},
});
followedBy.followedBy.map(async(user) => {
await prisma.user.update({
where: {id: user.id},
data: {
notifications: {
create: {
message: args.message,
watched: false,
},
},
},
});
});
But this isn't making the best of what Prisma offers.
As of September 2021, Prisma does not support mutating nested relations in a top-level updateMany query. This is what the typescript error is trying to tell you, that you can only access email, name, user_name and password fields inside data. There's an open feature request for this which you could follow if you're interested.
For the schema that you have provided, here's a possible workaround that's slightly less readable but more optimized than your current solution.
createNotification: async (_, args, {req, res}) => {
// get the followers array of req.userId
const followedBy = await prisma.user.findUnique({
where: { id: req.userId },
include: {
followedBy: true,
},
});
// array of notification objects to be created, one for each follower of req.userId
let messageDataArray = followedBy.followedBy.map((user) => {
return {
userId: user.id,
message: args.message,
watched: false,
};
});
// do a bulk createMany.
// Since it's one query, it should be more optimized than running an update for each user in a loop.
await prisma.notification.createMany({
data: messageDataArray,
});
};
If you're interested, here's the docs reference for the kinds of nested updates that are possible.
For the table which has Foreign key, I want to assign ManyToOne's decorator.
I know #ManyToOne(() => User, user => user.photos) is just table relation,
What its argument () => User, user => user.photos means?
And please tell me user: User's property and value mean.
import {Entity, PrimaryGeneratedColumn, Column, ManyToOne} from "typeorm";
import {User} from "./User";
#Entity()
export class Photo {
#PrimaryGeneratedColumn()
id: number;
#Column()
url: string;
#ManyToOne(() => User, user => user.photos)
user: User;
}
import {Entity, PrimaryGeneratedColumn, Column, OneToMany} from "typeorm";
import {Photo} from "./Photo";
#Entity()
export class User {
#PrimaryGeneratedColumn()
id: number;
#Column()
name: string;
#OneToMany(() => Photo, photo => photo.user)
photos: Photo[];
}
it just sets the inverse relationship so if you want you could query the other way back. For example:
await this.photoRepository.find({
loadEagerRelations: true,
relations: ['user'],
})
and you would have something like :
[
{
"id": 1,
"url": "https://twetew",
"user": {
"id": 1,
...
...
}
}
]
TypeORM needs this to understand the relation and create the correct reference. In your photos database table it will create a user_id column. User won't have a photo_id.
So:
#ManyToOne(() => User, user => user.photos)
user: User;
creates a user_id column on photos database table and lets you query the other way back.
I'm attempting to set up my User GraphQL model to have followers and following attributes to query on. However I'm having trouble setting up the relationship in Sequelize. I'm trying to use a Follower model as a Join Table and setup a BelongsToMany association, but haven't been able to get it working. Can anyone suggest what to do or point out what I'm doing wrong?
I've come up with a temporary solution by manually querying, which you can see in my User.model.ts, but I believe there is a better way to do it using proper configuration.
I'm using typescript wrappers around GraphQL and Sequelize, TypeGraphQL and sequelize-typescript respectively, as well as PostgreSQL.
User.model.ts
// VENDOR
import { ObjectType, Field, ID } from 'type-graphql';
import { Model, Table, Column, PrimaryKey, Unique, IsUUID, HasMany, DefaultScope, AllowNull, DataType, BelongsToMany } from 'sequelize-typescript';
// APP
import Post from '../post/post.types';
import Follower from '../follower/follower.types';
/** User model for GraphQL & Database */
#Table({ timestamps: false, tableName: 'users' }) // tell sequelize to treat class as table model
#DefaultScope(() => ({ include: [{ model: Post.scope(), as: 'posts' }] })) // tell sequelize to include posts in its default queries
#ObjectType() // tell GraphQL to treat class as GraphQL model
export default class User extends Model<User>{
#PrimaryKey
#Unique
#AllowNull(false)
#IsUUID(4)
#Column(DataType.UUID)
#Field(() => ID)
id: string;
#Unique
#AllowNull(false)
#Column
#Field()
ci_username: string;
#AllowNull(false)
#Column
#Field()
username: string;
#AllowNull(false)
#Column
#Field()
first_name: string;
#Column
#Field()
last_name: string;
#Column
#Field({ nullable: true })
profile_picture?: string;
// #BelongsToMany(() => User, { otherKey: 'user_id', as: 'followers', through: () => Follower })
// #Field(() => [User])
// followers: User[];
// MY TEMPORARY SOLUTION USING MANUAL QUERYING
#Field(() => [User])
get followers(): Promise<User[]> {
return Follower.findAll({ where: { user_id: this.id } })
.then(records => records.map(record => record.follower_id))
.then((follower_ids: string[]) => {
return User.findAll({ where: { id: follower_ids }});
})
}
// DOES NOT WORK, BUT I BELIEVE COULD POTENTIALLY LEAD TO BETTER SOLUTION
#BelongsToMany(() => User, { otherKey: 'follower_id', as: 'following', through: () => Follower })
#Field(() => [User])
following: User[];
#HasMany(() => Post)
#Field(() => [Post])
posts: Post[];
}
Follower.model.ts
// VENDOR
import { Model, Table, Column, PrimaryKey, Unique, IsUUID, AllowNull, DataType, Index, ForeignKey, AutoIncrement } from 'sequelize-typescript';
// APP
import User from '../user/user.types';
/** Follower model for Database */
#Table({ timestamps: false, tableName: 'followers' }) // tell sequelize to treat class as table model
export default class Follower extends Model<Follower>{
#PrimaryKey
#AutoIncrement
#Unique
#AllowNull(false)
#Column
id: number;
#AllowNull(false)
#IsUUID(4)
#Index
#ForeignKey(() => User)
#Column(DataType.UUID)
user_id: string;
#AllowNull(false)
#IsUUID(4)
#Index
#ForeignKey(() => User)
#Column(DataType.UUID)
follower_id: string;
}
GraphQL Query
{
users: allUsers {
id
username
first_name
last_name
following {
username
id
}
}
}
GraphQL Response / Error
{
"errors": [
{
"message": "Cannot return null for non-nullable field User.following.",
"locations": [
{
"line": 7,
"column": 5
}
],
"path": [
"users",
0,
"following"
],
"extensions": {
"code": "INTERNAL_SERVER_ERROR",
"exception": {
"stacktrace": [
"Error: Cannot return null for non-nullable field User.following.",
" at completeValue (/Users/jsainz237/Projects/trueview/trueview-api/node_modules/graphql/execution/execute.js:560:13)",
" at /Users/jsainz237/Projects/trueview/trueview-api/node_modules/graphql/execution/execute.js:492:16"
]
}
}
}
],
"data": null
}
Any help is appreciated.
You need to write a #FieldResolver manually that will resolve the relation and return proper data.
Another solution is to rely on ORM capabilities and lazy relations - when the returned base entity contains a promise as a field, so when .then() is called, it automatically fetches the relation for the database.
Creating a friends system in express/handlebars/mongoose. I'm using {{friends.length}} to show the total number of friends a user has, but how would I show the total types of friends. eg.
how many of those friends have status of 0 or 1.
0 = pending
1 = friends
How do I get the count of each through mongo/mongoose?
var UserSchema = new Schema({
username : String,
friends : [{ id: { type: Schema.Types.ObjectId, ref: 'User'}, status: Number }]
});
app.get('/:username/friends', function(req, res) {
User
.findOne({ username: req.params.username }, 'username friends')
.populate({
path: 'friends.id',
model: 'User',
select: 'username'
})
.exec(function(err, user) {
res.render('friends', user)
})
});
{
"_id" : ObjectId("590ac6b7663350948be1c085"),
"username" : "some username",
"friends" : [
{
"id" : ObjectId("590ac6ac663350948be1c083"),
"status" : 1,
"_id" : ObjectId("590ace171aa0aeb58f798466")
}
]
}
If I right understand you, you have array of all friends so you can use filter for it:
friendsType1 = friends.filter(friend=>friend.satatus===0);
friendsType2 = friends.filter(friend=>friend.satatus===1);
friendsType1.length;//total count friends with status 0
friendsType2.length//total count friends with status 1
Under UserSchema you can declare methods:
//find friends with status 1
UserSchema.statics.findFriends = function (username, clb){
mongoose.models.User
.findOne({ username: req.params.username }, 'username friends')
.populate({
path: 'friends.id',
model: 'User',
select: 'username'
})
.exec(function(err, user) {
if(err) return clb(err);
var result = user.friends.filter(friend=>friend.satatus===1);
clb(null, result);
})
}
Then you can use it like this:
app.get('/:username/friends', function(req, res) {
User.findFriends(req.params.username, function(err, users){
if(err) throw err;
res.render('friends', users);
});
});
I am learning GraphQL so I built a little project. Let's say I have 2 models, User and Comment.
const Comment = Model.define('Comment', {
content: {
type: DataType.TEXT,
allowNull: false,
validate: {
notEmpty: true,
},
},
});
const User = Model.define('User', {
name: {
type: DataType.STRING,
allowNull: false,
validate: {
notEmpty: true,
},
},
phone: DataType.STRING,
picture: DataType.STRING,
});
The relations are one-to-many, where a user can have many comments.
I have built the schema like this:
const UserType = new GraphQLObjectType({
name: 'User',
fields: () => ({
id: {
type: GraphQLString
},
name: {
type: GraphQLString
},
phone: {
type: GraphQLString
},
comments: {
type: new GraphQLList(CommentType),
resolve: user => user.getComments()
}
})
});
And the query:
const user = {
type: UserType,
args: {
id: {
type: new GraphQLNonNull(GraphQLString)
}
},
resolve(_, {id}) => User.findById(id)
};
Executing the query for a user and his comments is done with 1 request, like so:
{
User(id:"1"){
Comments{
content
}
}
}
As I understand, the client will get the results using 1 query, this is the benefit using GraphQL. But the server will execute 2 queries, one for the user and another one for his comments.
My question is, what are the best practices for building the GraphQL schema and types and combining join between tables, so that the server could also execute the query with 1 request?
The concept you are refering to is called batching. There are several libraries out there that offer this. For example:
Dataloader: generic utility maintained by Facebook that provides "a consistent API over various backends and reduce requests to those backends via batching and caching"
join-monster: "A GraphQL-to-SQL query execution layer for batch data fetching."
To anyone using .NET and the GraphQL for .NET package, I have made an extension method that converts the GraphQL Query into Entity Framework Includes.
public static class ResolveFieldContextExtensions
{
public static string GetIncludeString(this ResolveFieldContext<object> source)
{
return string.Join(',', GetIncludePaths(source.FieldAst));
}
private static IEnumerable<Field> GetChildren(IHaveSelectionSet root)
{
return root.SelectionSet.Selections.Cast<Field>()
.Where(x => x.SelectionSet.Selections.Any());
}
private static IEnumerable<string> GetIncludePaths(IHaveSelectionSet root)
{
var q = new Queue<Tuple<string, Field>>();
foreach (var child in GetChildren(root))
q.Enqueue(new Tuple<string, Field>(child.Name.ToPascalCase(), child));
while (q.Any())
{
var node = q.Dequeue();
var children = GetChildren(node.Item2).ToList();
if (children.Any())
{
foreach (var child in children)
q.Enqueue(new Tuple<string, Field>
(node.Item1 + "." + child.Name.ToPascalCase(), child));
}
else
{
yield return node.Item1;
}
}}}
Lets say we have the following query:
query {
getHistory {
id
product {
id
category {
id
subCategory {
id
}
subAnything {
id
}
}
}
}
}
We can create a variable in "resolve" method of the field:
var include = context.GetIncludeString();
which generates the following string:
"Product.Category.SubCategory,Product.Category.SubAnything"
and pass it to Entity Framework:
public Task<TEntity> Get(TKey id, string include)
{
var query = Context.Set<TEntity>();
if (!string.IsNullOrEmpty(include))
{
query = include.Split(',', StringSplitOptions.RemoveEmptyEntries)
.Aggregate(query, (q, p) => q.Include(p));
}
return query.SingleOrDefaultAsync(c => c.Id.Equals(id));
}