Exploding Kittens Card Game - React, Nodejs and Redis (Part 2)
Published: 2023-05-09 16:15:19

This is the second part of the series. In this part I'll be going over the backend implementation, the frontend code along with its implementation has been given in the first part, so you can check it out.

### Prerequisites 🖥️

 > - React (Basics)
 > - Nodejs (Basics)
 > - Typescript (Basics)

 >  Also, having Redis-insight installed is recommended. You can get it [here](https://redis.com/redis-enterprise/redis-insight/)





For this project we are going to be using **Redis Cloud**, just go [here](https://redis.com/try-free/) and signup to create an account (you can also install and setup a Redis database locally). 

I choose the free plan, where we get 30mb free storage, although it might feel less, but it's enough to get familiar with redis. 

After you signup inside subscriptions click on your database name. It's configuration page will open up, you will get the database public endpoint, username and password from here. We will be using these values to connect our application as well as redis-insight to our cloud database.

![Configuration Page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9hkel4rld2v2ygoiis9f.png)

![Security](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j8jlfot04uhmgp9ivkeg.png)
 
---

**Let's get started**

First let us install the required dependencies for this project - 

```
npm install express jsonwebtoken nodemon dotenv cors redis-om
```
>**You might be wondering, what is this `redis-om`?**
What **redis-om** basically does is provide a level of abstraction so that we can work with data without worrying about how it is actually stored inside of **Redis**.
For example redis-om provides us the functionality of storing and retrieving data in json format, however under the hood redis actually stores data in the form of key-value pairs.


**Here's the projects folder structure**

```
-
├── src
│   ├── controllers
│   ├── middleware
│   ├── routes
│   ├── schema
│   └── redisClient.ts
├── server.ts
├── tsconfig.json
├── package.json
└── package-lock.json
```


---

We will initiate the server inside `server.ts`

```javascript
//server.ts
import express from 'express';
import cors from 'cors';
import { authRouter } from './src/routes/userRoutes';

const app = express();
app.use(cors());

app.use(express.json());
const port = process.env.PORT ?? 5000;

app.listen(port, () => {
  console.log(`Server running on: http://localhost:${port}`);
});

app.use('/users', authRouter);
```


Then inside the `userRoutes.ts` file
```javascript
import express from 'express';
import { createUser, updateScores, getHighScores, signInUser } from '../controllers/userController';
import { checkAuth } from '../middleware/checkAuth';

const authRouter = express.Router();

authRouter.post('/signup', createUser);
authRouter.post('/login', signInUser);

authRouter.use(checkAuth)

authRouter.get('/updatescore', updateScores);
authRouter.get('/highest', getHighScores);

export { authRouter }
```
We set up the various routes and their respective controllers. On `/signup` and `/login` we will call the `createUser` and `signInUser` controllers, which we will create later. We we will add `checkAuth` middleware which checks the user authentication and saves current user's validated token to browser's local storage so that it can be sent to the server on subsequent requests.

**Now let's get to the Redis bit**
First we will connect to our Redis cloud database from our app.
The connection string will have the following format 

```
redis://<username>:<password>@<host:port or Public endpoint>
```
So inside `redisClient.ts` add the following code. 

```javascript
//redisClient.ts
import { Client } from 'redis-om';
import { config } from 'dotenv';
config()

const redisClient = new Client();

(async () => {
    await redisClient.open(`redis://${process.env.USER}:${process.env.PASS}@${process.env.URL}`)
})();
;

export { redisClient }
```
We import the **Client** class from **redis-om** and connect to the database by calling its **open** method, and then export the client.
Also, if the code block looks unfamiliar to you, then it's called **IIFE** and what it basically does is call the function as soon as it is defined. And as to why we need to use it, we use it to make typescript happy😅


Inside `userSchema.ts` add the following code.

```javascript
//userSchema.ts
import { Entity, Schema } from "redis-om"

interface User {
    password: string,
    email: string,
    score: number
}

class User extends Entity { }

export const userSchema = new Schema(User, {
    password: { type: 'string' },
    email: { type: 'string' },
    score: {
        type: 'number', sortable: true
    }
}, {
    dataStructure: "JSON"
})
```

Our `userSchema` consists of an `id`, `email`, `password` and a `score` field. Ideally we would have a seperate schema(i.e objects) such as scoreSchema for score and a seperate schema for the user's email, password and id. Then add a createdBy field to scoreSchema to denote to which user that score belongs to.Then filter the information based on userID. 
But here we going to everything in one single object. 
We also add an extra property `sortable` to score, this basically helps us in getting the sorted results faster.


By default entities map to JSON documents using RedisJSON but we can still define their type by.

```
{
    dataStructure: "JSON"
}

```
There are other options too, such as `HASH`


Then let us create a .env file for storing the environment variables

```
PORT=
#If your using redis cloud, user should be default
USER=default
#URL is the public endpoint, which you can find on your database configuration page, along with the password
URL=
PASS=
# jwt secret, you can write any string here
SECRET=
```

Now, we will create the middleware `checkAuth`, which checks and verifies user authentication using jwt.

```javascript
//checkAuth.ts
import jwt, { JwtPayload } from "jsonwebtoken"
import { Request, Response } from "express";

interface CustomRequest extends Request {
    currUserId?: any;
}

const checkAuth = async (req: CustomRequest, res: Response, next: () => void) => {
    const { authorization } = req.headers;

    if (!authorization) {
        return res.status(401).json({ error: "Authorization token not present" })
    }
    const token = authorization.split(' ')[1]

    try {
        const { id } = jwt.verify(token, process.env.SECRET as string) as JwtPayload

        req.currUserId = id  // add a new property to the req object to store current authenticated user's id.
        next();
    }
    catch (err) {
        res.status(401).json({ error: "Token not verified" })
    }
}

export { checkAuth }
```

We create `CustomRequest interface` because we are going to store the authenticated user's id(or entity Id) by adding a new property called `currUserId` on the `req` object so that the server can keep track of which user is making the requests.
This id is returned by the `jwt.verify` method after it verifies the token

Now lets create the controllers

``` javascript
//userControllers.ts
import { Request, Response } from 'express';
import { redisClient } from '../redisClient';
import { userSchema } from '../schema/userschema';
import jwt from "jsonwebtoken"

const userRepository = redisClient.fetchRepository(userSchema);
(async () => {
    await userRepository.createIndex();
})();

const createToken = (id: string) => {
    const token = jwt.sign({ id: id }, "amanandacateatfoodtogether", { expiresIn: '3d' });
    return token;
}

interface CustomRequest extends Request {
    currUserId?: any;
}

const createUser = async (req: CustomRequest, res: Response) => {
    try {
        const { email, password } = req.body

        let user = userRepository.createEntity({
            password: password,
            email: email,
            score: 0,
        })
        const id = await userRepository.save(user)
        const token = createToken(id)

        res.status(200).json({
            email: email,
            token: token
        })
    }
    catch (err: any) {
        res.status(401).json({ err: err.message })
    }
}

const signInUser = async (req: CustomRequest, res: Response) => {
    try {
        const { email, password } = req.body

        const userSearch: any = await userRepository.search() 
           .where('email').eq(email).where('password')
            .eq(password).return.first()

        if (!userSearch) {
            return res.status(401).json({
                msg: "invalid email or password"
            })
        }
        const token = createToken(userSearch?.entityId)

        res.status(200).json({
            email: userSearch.email,
            token: token
        })
    }
    catch (err: any) {
        res.status(401).json({ err: err.message })
    }
}

const updateScores= async (req: CustomRequest, res: Response) => {
    const user = await userRepository.fetch(req.currUserId);
    user.score = user.score + 1;
    await userRepository.save(user);

    res.send(user)
}

const getHighScores = async (req: CustomRequest, res: Response) => {
    const users = await userRepository.search()
        .sortDescending('score')
        .return.page(0, 10);  //page(offset, count) i.e start from 0 and go till 2 i.e returns 2 user highscores

    res.send(users);
}

export { createUser, updateScores, signInUser, getHighScores, userRepository }


```
First we create a repository called userRepository because inorder to work with entitites i.e to create, read entities we need access to that repository. Then inside the IIFE we create an Index on the repository, to get search functionality on a repository we first need to create an index on it.
Inside `creatUser` the `createEntity` method creates an entity but remember it doesn't save it to Redis, we do that by calling `save`. **This will return the `entityId` of the recently created entity(or object)**, which we then send to the `createToken` function to create a jwt token.
The `updateScores` function is called when a user wins a game and all it does is update the user's score in the database with 1.
The other controllers are quite simple.

With this we are done. 
You can the source code for the whole project [here](https://github.com/NabajitS/game-server)