You don't know Redis (Part 2)
Tags: webdev, javascript, tutorial, database
Published: 2021-06-22 19:26:32

---
title: You don't know Redis (Part 2)
published: true
description: How to create a registration and login flow backed by Redis
tags: webdev, javascript, tutorial, database
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/himpxy52mgds1cd68vr2.jpeg
---
In the first part of [You don't know Redis](https://dev.to/sandorturanszky/you-don-t-know-redis-3onh), I built an app using Redis as a primary database. For most people, it might sound unusual simply because the key-value data structure seems suboptimal for handling complex data models.

In practice, the choice of a database often depends on the application’s data-access patterns as well as the current and possible future requirements.

Redis was a perfect database for a [Q&A board](https://techforitrecruiters.com/questions/active). I described how I took advantage of [sorted sets](https://redis.io/topics/data-types#sorted-sets) and [hashes](https://redis.io/topics/data-types#hashes) data types to build features efficiently with less code.

Now I need to extend the Q&A board with registration/login functionality. 

I will use Redis again. There are two reasons for that.

Firstly, I want to avoid the extra complexity that comes with adding yet another database.

Secondly, based on the requirements that I have, Redis is suitable for the task. 

Important to note, that user registration and login is not always about only email and password handling. Users may have a lot of relations with other data which can grow complex over time. 

Despite Redis being suitable for my task, it may not be a good choice for other projects. 

Always define what data structure you need now and may need in the future to pick the right database. 

###Implementation
I use serverless functions, the [ioredis](https://github.com/luin/ioredis) library and [Upstash Serverless Redis](https://upstash.com/?utm_source=sndr_3).

I can’t help but talk about serverless all the time because it greatly simplifies development. I love when complexity is removed whenever possible and Upstash is doing just that for me. 

I have zero work with setting up Redis. Moreover, I am using Upstash both in development and production.

####Registration flow
During registration, we collect the user `name`, `email` and `password`. Before registering a user, we need to make sure that the email has not been registered already (is unique in the system).

Redis does not support constraints. However, we can keep track of all registered emails using a sorted set named `emails`. 

On every new registration, we can use the [ZSCORE](https://redis.io/commands/zscore) command to check whether the provided email is already registered.

If the email is taken, we need to notify the user about it. 

⚠️ Note, that this isn’t the best option because by telling that a given email is registered we provide a simple way for anyone to check whether someone is registered with a particular service, albeit it’s not a big security issue. 

Before we can save a new user, we need to: 
- **Generate a unique user `ID`.**

We can use the [INCR](https://redis.io/commands/incr) command to always get a unique value by incrementing a number stored at a key by one. If the key does not exist, Redis will set it to `0` before performing the operation. This means that the initial value will be `1`.
```
const id = await redis.incr('user_ids') // -> 1
```

Whenever you need to create a [counter](https://redis.io/commands/incr#pattern-counter), `INCR` is a great choice. Or you can build a [rate-limiter](https://redis.io/commands/incr#pattern-rate-limiter) to protect your API from being overwhelmed by using `INCR` together with [EXPIRE](https://redis.io/commands/expire). 


- **Hash the password with the [bcrypt](https://github.com/kelektiv/node.bcrypt.js/) library.**

```
const hash = await bcrypt.hash(password, 10)
```

Now that we have the unique user `ID` (e.g. user ID is 7) and the hashed password, we can:
**1. Store user details in a hash under the `user:{ID}` key.**

```
redis.hmset('user:7', { 7, name, email, hash })
```

Knowing the `ID`, we can easily get all user details using the [HGETALL](https://redis.io/commands/hgetall) command:

```
redis.hgetall('user:7');
```

**2. Add the user’s email to the `emails` sorted set.**

```
redis.zadd('emails', -Math.abs(7), email)
```

This allows us to lookup emails to check if they are registered or get the user's `ID` by `email` which is exactly what we need for the login process. 

`redis.zscore('emails', email)` will return the score which is the `ID` or `nil` if the email is not found.

Notice how we use this sorted set for two important features, namely ensuring unique emails and looking up users by email. 

But we are taking it one step further and set scores (which represent user `ID`s) as negative numbers to mark emails as unverified: `-Math.abs(7)`. Then, when the email is verified, we simply convert it to a positive number.

```
redis.zadd('emails', Math.abs(7), email)
```
If a specified `email` is already a member of the `emails` sorted set, Redis will update the score only.

During the login process, we can always check for negative numbers and request users to verify their email instead of logging them in.

Retrieving all unverified emails is a trivial operation done with the [ZRANGEBYSCORE](https://redis.io/commands/zrangebyscore) command.

```
redis.zrangebyscore('emails', '-inf', -1, 'WITHSCORES');
```
Registration function [source code](https://github.com/sandorTuranszky/questions-and-answers-board-built-with-redis/blob/main/lambda/register.js)

####Login flow
Before logging in the user, we check if the provided email exists in our database. As mentioned before, the `score` is the user `ID`.
```
const userId = await redis.zscore('emails', email);
```

If so, we first check if the email is verified by making sure the `ID` is a positive number. If not, we ask users to verify their email.

If the email is verified, we get the password hash that we stored for the user:

```
const hash = await redis.hget('user:7', 'hash');
```

and check whether the password is correct:
```
const match = await bcrypt.compare(password, hash);
```

If the password is correct, we generate a token and return it to the client.

And we are done.

Login function [source code](https://github.com/sandorTuranszky/questions-and-answers-board-built-with-redis/blob/main/lambda/login.js)

###Conclusion
As you can see, we needed four Redis commands for registration and only two for login. 

Probably you noticed that while describing the registration and login process with Redis we also revealed two more use cases for Redis, namely counter and rate-limiting.

Redis has a lot more [use cases](https://redislabs.com/redis-best-practices/introduction/) beyond cache and learning about them will only make you even more efficient.

Follow me to read about how I am implementing a secure production-ready registration flow with email verification and password recovery backed by Redis. 

---

Check out my article on how I implemented the [LinkedIn-like reactions with Serverless Redis](https://dev.to/sandorturanszky/how-to-create-linkedin-like-reactions-with-serverless-redis-4cad).