All Articles

Creating a Reddit Clone Using React and GraphQL - 17

inner peace

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.

  1. User can upvote or downvote many posts.
  2. 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.tsfile.

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 gencommand 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