Let's build our own URL Shortener with NestJS and Redis
Welcome! Today, we'll create our very own URL shortener using NestJS and Redis. If you've ever surprised at the simplicity of popular services like TinyURL and wondered how they transform long URLs into compact links, you're in the right place.
The concept is quite straightforward: users provide a lengthy URL, and our system assigns a unique identifier, in this case, a simple UUID, to represent it. This unique pairing is then stored as a key-value duo in our Redis database. But why bother with URL shorteners?
URL shorteners aren't just about brevity; they significantly enhance the sharing experience. Whether you're sharing links on social media or simplifying URLs for ease of use, this project has practical applications.
Example key-value
Understanding the Structure
Let's break down the key-value structure that combines a (UUID) with user information that we are going to store in our redis db:
- [UUID]: A unique identifier for each entry.
- [userId|userName]: Information about the user associated with the shortened URL.
- longUrl: The original URL that has been shortened.
First things first, let's set up a Redis. Head over to Upstash Redis Console to get your keys, which will look something like this:
Env key setup
With the grunt work behind us, give yourself a well-deserved pat on the back. Now, let's dive into the juicy part.
Setting up our NestJS
Let's first clone the starter repo:
And, install @upstash/redis
and some depedencies:
We need @upstash/redis
to store our shortened urls and @nestjs/config
to load env files into our project.
Here is our file structure:
Before move on let's update our .env
as follows:
And, now it's time to make our redis-client.ts
this is our entry file to our redis instance.
In NestJS since we can't access env keys directly within our function we have to pass it from the top when we load the env keys with @nestjs/config
. To be able to use @nestjs/config
we have to add it into
our import in app.module.ts
.
NestJS has opiniated way of doing things. .module.ts
files are similar to barel import files. They control what you import from or export to a specific module. As you can see we are also passing
our app.controller.ts
and app.service.ts
which we'll get to it in a second.
Now that we've successfully integrated the modules, let's delve into the functionality of service files and explore the process of injecting environment keys into app.service.ts
.
NestJS uses decorators extensively for abstraction. When we apply the @Injectable
decorator, it signals that other packages can now inject this module into their runtime. With this foundation, let's proceed to inject our environment keys.
NestJS adopts a developer-friendly approach by favoring Dependency Injection over explicit imports/exports. This streamlined method is how we easily imported environment keys into our Redis client.
Now, let's bring our first actual function setShortUrlToCache
.
Since we are essentially in a class, we can use this
to access other internal methods like this.getRedis()
.
This function requests a link to shorten, an optional expiration time, and a userId to easily identify shortened URLs.
Then, We create a UUID using nanoid and the userId: ${nanoid(UUID_LENGTH)}:${userId}
, and then call redis.set
with the given pathKey
, payload
, and ex
.
If the set operation is successful, we return pathKey
to the user so they can later retrieve their full URL.
Now, we need two more method to get single shortened urls and all the shortened urls with associated user.
The first method is straightforward: we pass the pathKey and retrieve the full URL along with additional metadata.
The second method takes a userId, attempts to retrieve all keys containing that userId using *userId*
, and then calls redis.mget to fetch all of them simultaneously.
Now, we need a controller file to expose those services to endpoints
As mentioned earlier, NestJS heavily relies on decorators, such as @Post
and @Get
. If you've worked with Spring or .Net, the controller structure might seem familiar.
In our case, we use @Post()
to define an endpoint, in this instance, /shorter. We then use @Req() req: Request to access query parameters. After that, the process is straightforward – we call this.appService.setShortUrlToCache
, which we injected earlier through constructor injection, and return the pathKey to the user.
If you wanna test /shorten
. Use this example cURL,
In return you will get something like this
To get back our full url we simply do this
In return you will get something like this
To get every associated long url:
In return you will get something like this
Wrap up
To sum it up, our journey to build a URL shortener using NestJS and Redis has come to a end. Throughout this process, we've seen how the combination of NestJS and Redis, particularly Upstash Redis, can streamline the development of efficient and practical applications. The project not only showcases the technical capabilities of these technologies but also emphasizes their real-world applicability in creating useful tools like URL shorteners. As you continue exploring and building with these tools, remember the valuable insights gained from this project, and consider how they can be applied to future endeavors. Happy coding!