[ PART 9 ] Creating a Twitter clone with GraphQL, Typescript, and React ( isLiked? )
Tags: showdev, career, javascript, graphql
Published: 2021-01-12 11:36:08

Hi everyone! ;)

As a reminder, I try to do this challenge mostly to learn about GraphQL ;): [Tweeter challenge](https://devchallenges.io/challenges/rleoQc34THclWx1cFFKH)

[Github repository](https://github.com/ips-coding-challenge/challenge_twitter)

[Db diagram](https://dbdiagram.io/d/5f75c0243a78976d7b75f7be)

In this post, we will see how to check if a tweet in our feed is already liked by the connected user. I had some "issues" while implementing this feature and even if it works, I wonder if there are better options to achieve the same result. Feel free to share how I could have done it if you know better ways.

First of all, let's add the *isLiked* field to our Tweet entity:

```typescript
@Field()
isLiked: boolean
```

I know I will need to create a **dataloader** but in this case, I will have to know about the connected user to check if the user liked the tweet. If I need the user, it means that I also need to add the **@Authorized()** annotation to the **@FieldResolver()**. At first, when I started this application, I wanted that only connected users should be able to access the tweets.

I'd stick with that idea, but I still wanted to see how I could deal with the fact that some properties shouldn't necessarily return an authentication error. This is the case for the *isLiked* property I think. When a user is connected, you have to check if the user has already liked this tweet but if I don't have a user, I just need to return false. But if I pass the annotation **@Authorized()** to my **@FieldResolver()**, it will throw an error. Fortunately, our authChecker method allows us to pass a second parameter called **role**. So here's what the new version of my authChecker will look like:

*src/middleware/authChecker.ts*

```typescript
import { AuthChecker } from 'type-graphql'
import { MyContext } from '../types/types'
import { extractJwtToken } from '../utils/utils'
import { verify } from 'jsonwebtoken'
import { JWT_SECRET } from '../config/config'
import { AuthenticationError } from 'apollo-server'

export const authChecker: AuthChecker<MyContext, string> = async (
  { root, args, context, info },
  roles
) => {
  const {
    db,
    req,
    dataloaders: { userDataloader },
  } = <MyContext>context

  try {
    const token = extractJwtToken(req)
    const {
      data: { id },
    }: any = verify(token, JWT_SECRET as string)

    const user = await userDataloader.load(id)

    if (!user) {
      throw new AuthenticationError('User not found')
    }

    context.userId = user.id
    return true
  } catch (e) {
    if (roles.includes('ANONYMOUS')) {
      context.userId = null
      return true
    }
    throw e
  }
}

```
I do a try/catch to avoid throwing the error if I allow the role "ANONYMOUS". For the moment, the only problem I see is that a "TokenExpired" error should trigger the error in order to be able to catch that in the Frontend to do what's appropriate. It should be enough to check the error type to handle this case ;). 

So here's what the **@FieldResolver()** and the **dataloader** look like:

*src/resolvers/TweetResolver.ts*

```typescript
@FieldResolver(() => Boolean)
@Authorized('ANONYMOUS')
async isLiked(@Root() tweet: Tweet, @Ctx() ctx: MyContext) {
    const {
        userId,
        dataloaders: { isLikedDataloader },
    } = ctx

    if (!userId) return false

    const isLiked = await isLikedDataloader.load({
        tweet_id: tweet.id,
        user_id: userId,
    })

    return isLiked !== undefined
}
```

*src/dataloaders/dataloaders.ts*

```typescript
isLikedDataloader: new DataLoader<any, any, unknown>(async (keys) => {
    const tweetIds = keys.map((k: any) => k.tweet_id)
    const userId = keys[0].user_id

    const likes = await db('likes')
      .whereIn('tweet_id', tweetIds)
      .andWhere('user_id', userId)
    return tweetIds.map((id) => likes.find((l) => l.tweet_id === id))
  }),
```

As you can see, I pass an object for the *keys* of the "dataloader" since I need the **user_id**. Also, in the "authChecker" method, I set the userId to null if I was in "ANONYMOUS" mode. So, if I don't have a user logged in, I return **false** directly. Otherwise, I make my little query in the "dataloader" to be able to retrieve what I need ;).

![Feed query with isLiked property and user connected](https://dev-to-uploads.s3.amazonaws.com/i/yxt3galj6c0idrpstcpy.png)

And without a connected user
![Feed query with isLiked property and no user connected](https://dev-to-uploads.s3.amazonaws.com/i/90bflzq2t29cidxrmezo.png)

This is how I handled this "problem". I'm sure there are better/scalable ways and I started to read about some possibilities. But for now, the idea is to resolve problems that I encountered, and not to overshadow Twitter :D.

Have a nice day and see you in the next part ;).