This blog post originally posted on my blog site and you can find it here.
From the last post, we are able to get posts successfully. It is better to show who wrote this post in the UI. So, let's change the post resolver. To do that we need to write a join query with Post
and User
entities.
Add the below code to posts
resolver method to get the user data.
// query parameters array
const replacement: any[] = [realLimitPlusOne];
if (cursor) {
replacement.push(new Date(parseInt(cursor)));
}
// create a join query
const posts = await getConnection().query(
// make attention on json_build_object method
// this will form this result as expected return type
`
SELECT p.*,
json_build_object(
'id', u.id,
'username', u.username,
'email', u.email
) creator
FROM post p
INNER JOIN public.user u on u.id = p."creatorId"
${cursor ? ` WHERE p."createdAt" < $2` : ""}
ORDER BY p."createdAt" DESC
LIMIT $1
`,
replacement
);
In the above code, any user can see any email address. So let's add a mask to the email field on user
resolver. Only logged user can see own email address.
@Resolver(User) // add the resolver type
export class UserResolver {
// add the field resolver
@FieldResolver(() => String)
email(@Root() user: User, @Ctx() { req }: RedditDbContext) {
// only logged user can see his/her email address.
if (req.session.userId === user.id) {
return user.email;
}
return "";
}
Now, we are going to add the Upvote and Downvote functionality. Here we need to have Many-to-Many
relationship with User
and Post
entities.
- User can upvote or downvote many posts.
- Post can have many votes from many users.
First, we are adding the Upvote
entity.
@ObjectType()
@Entity()
export class Upvote extends BaseEntity {
@Column({ type: "int" })
value: number;
@PrimaryColumn()
userId: number;
@ManyToOne(() => User, (user) => user.upvotes)
user: User;
@PrimaryColumn()
postId: number;
@ManyToOne(() => Post, (post) => post.upvotes)
post: Post;
}
Within this entity, you will that it has a relationship mappers to User
and Post
entities. So, now we need to add those mappers back to those entities.
// inside the Post entity add below code
@OneToMany(() => Upvote, (upvote) => upvote.post)
upvotes: Upvote[];
// inside the User entity add below code
@OneToMany(() => Upvote, (upvote) => upvote.user)
upvotes: Upvote[];
Now, most importantly we need to update the entities in index.ts
file.
const conn = await createConnection({
// ... removed for clarity
entities: [Post, User, Upvote],
// ... here we added Upvote entity
} as any);
Now we need to add the mutation to post
resolver. Here we need to add a SQL transaction for this functionality. Because in here we need to update 2 tables. One is and a new record for upvote
table. After that, we need to update the new count of upvotes in post
table.
Here is vote
mutation code.
@Mutation(() => Boolean)
@UseMiddleware(isAuth)
async vote(
@Arg("postId", () => Int) postId: number,
@Arg("value", () => Int) value: number,
@Ctx() { req }: RedditDbContext
) {
const isUpvote = value !== -1;
const realValue = isUpvote ? 1 : -1;
const { userId } = req.session;
await Upvote.insert({
userId,
postId,
value: realValue,
});
await getConnection().query(
`
START TRANSACTION;
INSERT INTO upvote ("userId", "postId", value)
VALUES (${userId}, ${postId}, ${realValue});
UPDATE post p
SET p.points = p.points + ${realValue}
where p.id = ${postId};
COMMIT;
`
);
return true;
}
Now our backend code is complete for those two functionalities. Let’s change the front-end code.
First, we can change the posts graphql query
to get the user name.
// change the Posts query by adding this code lines
// inside the posts
creator {
id
username
}
Now run yarn gen
command to generate the new changes for the graphql.tsx
file.
Now add the below line to show the user name in the UI.
<Box key={p.id} p={5} shadow="md" borderWidth="1px">
<Heading fontSize="xl">{p.title}</Heading>
// we can show the user name by adding below line.
<Text>posted by {p.creator.username}</Text>
<Text mt={4}>{p.textSnippet} </Text>
</Box>
And the last for this post we are making some changes to invalidate the cache when the user adds a new post. It will make sure that user will see new posts when the user redirects to the home page.
Let’s add this mutation to createUrqlClient
method in the web app.
Mutation: {
// new mutation for create post
createPost: (_result, args, cache, info) => {
cache.invalidate("Query", "posts", {
limit: 15,
});
},
// ... rest of the code
Thanks for reading this. If you have anything to ask regarding this please leave a comment here. Also, I wrote this according to my understanding. So if any point is wrong, don’t hesitate to correct me. I really appreciate you.
That’s for today friends. See you soon. Thank you.
References:
This article series based on the Ben Award - Fullstack React GraphQL TypeScript Tutorial. This is amazing tutorial and I highly recommend you to check that out.
Main image credit
Top comments (0)