skip to Main Content

I’m kind of new to Redis and I’m currently experiencing a project stand-still because I don’t know any other way to set and get in Redis.

My problem is I’m building a url shortener and when the user posts (a POST request) a url to the server, I’m setting the url as the key and a nanoid generated code as the value and sending back the nanoid code to the user. But when the user sends a GET request with the url code to the server I have to check if the url is already cached and redirect the user to the url but I can’t because the actual url as been set as the key not the url code so it will always return undefined. Please can you help me with this problem? Is there some other to do this? Many thanks in advance! Here is the code:

import redis from 'redis';
import http from 'http';
import express from 'express';
import { Router } from 'express';
import { promisify } from 'util';
import { nanoid } from 'nanoid';

interface Handler {
  (req: Request, res: Response, next: NextFunction): Promise<void> | void;
}

interface Route {
  path: string;
  method: string;
  handler: Handler | Handler[];
}

const { PORT = 8080} = process.env;

// I'm using a docker container
const { REDIS_URL = 'redis://cache:6379' } = process.env;

const redisClient = redis.createClient({
  url: REDIS_URL
});

const initCache = async () =>
  new Promise((resolve, reject) => {
    redisClient.on('connect', () => {
      console.log('Redis client connected');
      resolve(redisClient);
    });

    redisClient.on('error', error => reject(error));
  });

async function getShortenedURL(url: string) {
    const urlCode = nanoid(7);
    redisClient.setex(url, 3600, urlCode);
    return urlCode;
}

const getAsync = promisify(redisClient.get).bind(redisClient);

async function getFromCache(key: string) {
  const data = await getAsync(key);
  return data;
}

const routes = [
    {
        path: '/:url',
        method: 'get',
        handler: [
            async ({ params }: Request, res: Response, next: NextFunction) => {
                try {
                    const { url } = params;
                    const result = await getFromCache(url);

                    if (result) {
                       res.redirect(301, result);
                    } else {
                       throw new Error('Invalid url');
                    }
                } catch (error) {
                    console.error(error);
                }
            }
        ]
    },
    {
        path: '/api/url',
        method: 'post',
        handler: [
            async ({ body }: Request, res: Response, next: NextFunction) => {
                const { url } = body;
                const result = await getFromCache(url);
                result ? res.status(200).send(`http://localhost:${PORT}/${result}`) : next();
            },
            async ({ body }: Request, res: Response) => {
                const result = await getShortenedURL(body.url as string);
                res.status(200).send(result);
            }
        ]
    }
];

const applyRoutes = (routes: Route[], router: Router) => {
    for (const route of routes) {
        const { method, path, handler } = route;
        (router as any)[method](path, handler);
    }
};

const router = express();
applyRoutes(routes, router);


const server = http.createServer(router);


async function start() {
    await initCache();
    server.listen(PORT, () => {
        console.log(`Server is running on http://localhost:${PORT}...`)
    }
    );
}

start();


2

Answers


  1. you should use the HashCode generate for the URL as the Key for your dictionary since you intend to lookup by the shortened URL later.

    Post–> Hash the URL, Encode it as per your need for length restrictions return the shortened Key as shortened URL and put <Hash,URL> in your map
    Get–> User gives the shortened Key, Dictionary lookup for shortened Key and return the actual URL.

    Login or Signup to reply.
  2. As I understand, you need to make sure that you do not shorten and store any given url twice.

    You could encode the url and use it as the sort version and as a key at the same time. E.g.

    www.someurltoshorten.com -> encoded value -> 
    {key: value} -> encoded value: www.someurltoshorten.com
    

    If a user wants to shorten a url, you encode it first and you should get the exact same hash for the exact same url.

    Once you get the encoded value, you can use the SET command with a "GET" option. You can also use the expire (EXAT) option to clean up old urls (those that nobody is looking for anymore) using the feature that is built in Redis.

    It will do the following for you:

    1. Set key to hold the string value (the key is the short version of the url and the value is the url itself)

    2. If the value exists, it will overwrite it and reset (extend) the TTL (time to live) if you set it.

    3. And the "GET" option will return the old value if it exists or null.

    With one command you will be able to:

    1. Create a value in Redis
    2. Get the value if it already exists resetting the TTL (it makes sense to extend it) and all of the without any extra code with one command only!!!

    The flow may look as follows:

    1. A user inputs a url to be shortened:
    • you encode the url
    • you store it in Redis using the SET command where the key is the encoded value and the value is the url.
    • you return the encoded value which you already now. There is no need to check whether the url has already been shortened once because the SET command will either create a new entry or update the existing once.
    1. A user inputs a shortened url
    • you encode the url
    • you store it in Redis using the SET command where the key is the encoded value and the value is the url.
    • you get the url from the value that was returned by the SET command thank to the "GET" option.

    The only difference between the two cases is in whether you return the shortened url or the normal url

    Basically, you need one Redis command for all of that to work.

    I did not test the encoding/hashing of the url and it may not work with all types of url. You need to check which encoding would cover all cases.

    But the idea here is the concept itself. It’s similar to how we handle passwords. When you register, it’s hashed. Then, when you log in and provide the same password, we can hash it again and compare hashes. Secure hashing with bycript, as an example, can be expensive (can take a lot of time).

    For urls you need to make sure that encoding/hashing always produces the same result for the same url.

    Keep in mind the length of the keys as describe here https://redis.io/topics/data-types-intro#redis-keys

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search