Designing an Effective URL Shortening Service

Kayvan Kaseb
Software Development
11 min readApr 7, 2024
The picture is provided by Unsplash

URL shortening services play a vital role in simplifying long and complex links into shorter and more manageable ones. Besides, it enhances sharing efficiency and reducing errors. Even though the automatic link shortening features of social media platforms, URL shortening service remain invaluable for scenarios like business cards, print ads, or podcast interviews where clickable links are unavailable. This article explores the design and implementation of a URL shortening service to meet various functional and non-functional requirements. By addressing these requirements and considerations, the proposed URL shortening service aims to provide a user-friendly, efficient, and secure solution for shortening and managing URLs.

Introduction

A URL Shortening Service simplifies long and complex links into shorter, more manageable ones. This makes sharing easier and diminishes the errors when typing lengthy URLs. This URL version is more user-friendly and easier to share, particularly in cases such as business cards, print ads, or podcast interviews where clickable links are not available. Even though social media platforms and messaging apps often automatically shorten links to fit character limits, URL shortening services remain valuable for including additional information, like tracking parameters, deep linking within apps, or conveying complex data such as GPS coordinates. They also enable sending users with different devices to tailored destinations, such as directing them to the appropriate app store for downloading an app. There are some popular services for that purpose on the market. For instance:

Bitly for the best all-around URL shortener
Rebrandly for an alternative to Bitly
TinyURL for free, fast, and anonymous short URLs
BL.INK for business owners
URL Shortener by Zapier for automatically creating short links
Short.io for a great free plan

The reasons to shorten a URL are the following:

  • Track clicks for providing insights for analysis
  • Beautify a URL for readability purpose
  • Disguise the underlying URL for affiliates (Hide Affiliate Links)
  • Fit message length limits: some instant messaging services limit the count of characters on the URL

The following code is a simple example of shortening a URL using the Bitly API in Java:

import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Base64;
import java.util.Scanner;

import com.google.gson.Gson;

public class URLShortener {

private static final String BITLY_API_URL = "https://api-ssl.bitly.com/v4/shorten";
private static final String ACCESS_TOKEN = "YOUR_ACCESS_TOKEN_HERE";

public static String shortenURL(String longURL) throws IOException {
URL url = new URL(BITLY_API_URL);

HttpURLConnection conn = (HttpURLConnection) url.openConnection();

conn.setRequestMethod("POST");

conn.setRequestProperty("Authorization", "Bearer " + ACCESS_TOKEN);
conn.setRequestProperty("Content-Type", "application/json");

conn.setDoInput(true);
conn.setDoOutput(true);

String jsonInputString = "{\"long_url\": \"" + longURL + "\"}";

conn.getOutputStream().write(jsonInputString.getBytes());

int responseCode = conn.getResponseCode();

if (responseCode == HttpURLConnection.HTTP_OK) {
Scanner scanner = new Scanner(conn.getInputStream());
StringBuilder responseBuilder = new StringBuilder();
while (scanner.hasNextLine()) {
responseBuilder.append(scanner.nextLine());
}
scanner.close();
Gson gson = new Gson();
BitlyResponse bitlyResponse = gson.fromJson(responseBuilder.toString(), BitlyResponse.class);

return bitlyResponse.link;
} else {
System.out.println("Error: " + responseCode + " - " + conn.getResponseMessage());
return null;
}
}

public static void main(String[] args) {
String longURL = "https://www.sample.com/very/long/url/that/needs/to/be/shortened";
try {
String shortURL = shortenURL(longURL);
if (shortURL != null) {
System.out.println("Shortened URL: " + shortURL);
}
} catch (IOException e) {
System.out.println("An error occurred: " + e.getMessage());
}
}
}

class BitlyResponse {
String link;
}

This Java code sends a POST request to the Bitly API with the long URL in the request body and your Bitly access token in the Authorization header. Then, it parses the JSON response to extract the shortened URL.

public class Main {
public static void main(String[] args) {
String longURL = "https://www.sample.com/very/long/url/that/needs/to/be/shortened";

try {
String shortURL = URLShortener.shortenURL(longURL);

if (shortURL != null) {
System.out.println("Shortened URL: " + shortURL);
} else {
System.out.println("Failed to shorten the URL.");
}
} catch (IOException e) {
System.out.println("An error occurred: " + e.getMessage());
}
}
}

To build a URL Shortening Service, some basic functional and non-functional requirements can be defined as follows:

Functional Requirements:

1.Our service must generate a unique and shorter alias for a given URL.

2. Upon accessing a short link, users should be redirected to the original URL.

3. Users should have the option to choose a custom alias for their URL.

4. Links should expire after a default period, and users should be able to set the expiration time.

Non-Functional Requirements:

1. The system must maintain high availability to ensure uninterrupted URL redirection.

2. URL redirection must occur instantly with minimal latency to provide a seamless user experience.

3. Shortened links must be non-predictable and not easily guessable for security reasons.

Additionally, you can be able to implement some features like analytics to track metrics, such as number of redirections for each short link in future.

Required API Endpoints

Once the requirements are settled, it is important to clearly define the system APIs. This specifies what the system should do. A URL shortening service primarily requires two API endpoints:

URL Shortening: To create a new short URL, clients send a POST request with a single parameter, the original long URL, to the following API endpoint:

  • POST api/v1/data/shorten
  • Request parameter: {longUrl: longURLString}
  • Response: shortURL

URL Redirecting: Clients send a GET request to redirect a short URL to the corresponding long URL using the following API endpoint:

  • GET api/v1/shortUrl
  • Response: longURL for HTTP redirection

This is a simple implementation of the URL shortening service API in Java using the Spring Boot framework:

  1. Model Class:
import java.time.LocalDateTime;

public class URL {
private Long id;
private String longUrl;
private String shortUrl;
private LocalDateTime createdAt;
private LocalDateTime expiresAt;
private int hits;

// Getters and setters..
}

2. Repository Interface:

import org.springframework.data.repository.CrudRepository;

public interface URLRepository extends CrudRepository<URL, Long> {
URL findByShortUrl(String shortUrl);
}

3. Service Class:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.Random;

@Service
public class URLService {
@Autowired
private URLRepository urlRepository;

public String generateShortUrl() {
String characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
StringBuilder shortUrl = new StringBuilder();
Random random = new Random();
for (int i = 0; i < 6; i++) {
shortUrl.append(characters.charAt(random.nextInt(characters.length())));
}
return shortUrl.toString();
}

public URL shortenUrl(String longUrl, LocalDateTime expiresAt) {
String shortUrl = generateShortUrl();
URL url = new URL();
url.setLongUrl(longUrl);
url.setShortUrl(shortUrl);
url.setCreatedAt(LocalDateTime.now());
url.setExpiresAt(expiresAt);
url.setHits(0);
return urlRepository.save(url);
}

public URL expandUrl(String shortUrl) {
return urlRepository.findByShortUrl(shortUrl);
}
}

4. Controller Class:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.time.LocalDateTime;

@RestController
@RequestMapping("/api")
public class URLController {
@Autowired
private URLService urlService;

@PostMapping("/shorten")
public URL shortenUrl(@RequestBody String longUrl, @RequestParam(required = false) LocalDateTime expiresAt) {
return urlService.shortenUrl(longUrl, expiresAt);
}

@GetMapping("/expand/{shortUrl}")
public URL expandUrl(@PathVariable String shortUrl) {
return urlService.expandUrl(shortUrl);
}
}

5. Application Class:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}

This setup will create a RESTful API with two endpoints:

  • /api/shorten (POST): Accepts a long URL and an optional expiration date and returns the corresponding short URL.
  • /api/expand/{shortUrl} (GET): Accepts a short URL and returns the corresponding long URL.

Handling Redirect

To implement a redirect mechanism in Java that takes a short URL, looks up the corresponding long URL in the database, and redirects the user’s browser to the long URL, you can follow these steps:

  1. Retrieve the long URL associated with the given short URL from the database.
  2. If the long URL exists, perform a 301 (permanent) or 302 (temporary) redirection to the long URL.
  3. If the long URL does not exist, handle the case gracefully, such as by redirecting to an error page or displaying a custom error message.

Basically, a 301 redirect signifies that the requested URL has been permanently moved to the long URL. As a result, the browser caches this response, and subsequent requests for the same URL are directly sent to the long URL server without involving the URL shortening service. On the other hand, a 302 redirect indicates a temporary move of the URL to the long URL. This means that subsequent requests for the same URL are first directed to the URL shortening service before being redirected to the long URL server. Each redirection method has its advantages and disadvantages. If the primary concern is to reduce server load, utilizing a 301 redirect is logical as only the initial request for the same URL is forwarded to the URL shortening servers. Nevertheless, if analytics holds significance, a 302 redirect is preferable as it facilitates tracking click rates and the source of clicks more effectively.

In fact, URL redirecting can be efficiently implemented using hash tables.
A hash table, known as a hash map or dictionary, is a data structure that stores key-value pairs. It uses a hash function to compute an index into an array of buckets or slots, from which the desired value can be found. Hash tables offer efficient insertion, deletion, and lookup operations, typically with an average-case time complexity of O(1) for these operations. So, by storing <shortURL, longURL> pairs in the hash table, the redirection process becomes straightforward:

  1. Retrieve the longURL corresponding to the given shortURL from the hash table using hashTable.get(shortURL).
  2. Once the longURL is obtained, perform the URL redirect to direct the user’s browser to the longURL.

Designing the Database

  • Use a database to store mappings between short URLs and their corresponding long URLs.
  • Consider using a NoSQL database like MongoDB or a distributed key-value store like Redis for scalability.
  • Include additional metadata such as creation date, expiration date, and usage statistics.

NoSQL databases use a variety of data models for accessing and managing data. These types of databases are optimized specifically for applications that require flexible data models, large data volume, and low latency, which are achieved by relaxing some of the data consistency restrictions of relational databases.

Redis: The in-memory data store used by millions of developers as a cache, vector database, document database, streaming engine, and message broker.

Shortening Algorithm

  • Choose a method to generate short URLs.
  • Common methods include generating a random string of characters, hashing the long URL to generate a unique identifier, or encoding the database ID of the long URL.
  • Ensure that generated short URLs are unique to avoid collisions.

The shortening algorithm is a crucial component of a URL shortening service. It is responsible for generating unique, compact identifiers (short URLs) from longer URLs. Although there are different approaches to designing a shortening algorithm, the key goals typically include creating short URLs that are:

  1. Unique: Each long URL should map to a distinct short URL to avoid collisions.
  2. Compact: Short URLs should be as concise as possible to minimize length and improve usability.
  3. Non-predictable: Short URLs should not be guessable or easily discoverable. This enhances security and preventing unauthorized access.

Here are a few common techniques for implementing a shortening algorithm:

  1. Hashing: Use a cryptographic hash function (e.g., MD5, SHA-256) to generate a fixed-length hash of the long URL. The hash is then encoded into a short string using base64 or a similar encoding scheme. While this approach guarantees uniqueness, it may result in longer short URLs and is susceptible to collision attacks.
  2. Base Conversion: Convert the unique identifier of the long URL (e.g., a database auto-increment ID) into a base-n representation (e.g., base62) to generate a short URL. This method produces shorter URLs, but may require additional processing to ensure uniqueness.
  3. Random Generation: Generate a random string of characters or digits to create short URLs. While simple and effective, this approach may lead to collisions and longer short URLs as the system scales.
  4. Combination of Techniques: Combine hashing, base conversion, and random generation techniques to create a hybrid shortening algorithm that balances uniqueness, compactness, and security.

For instance, here is a simplified example of a base conversion shortening algorithm in Java:

public class URLShortener {
private static final String BASE62_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
private static final int BASE62_LENGTH = BASE62_CHARS.length();

public String shortenURL(String longURL) {
long id = generateUniqueID(longURL);

return toBase62(id);
}

private long generateUniqueID(String longURL) {

return longURL.hashCode();
}

private String toBase62(long number) {
StringBuilder shortURL = new StringBuilder();
while (number > 0) {
shortURL.insert(0, BASE62_CHARS.charAt((int) (number % BASE62_LENGTH)));
number /= BASE62_LENGTH;
}
return shortURL.toString();
}

public static void main(String[] args) {
URLShortener shortener = new URLShortener();

String longURL = "https://www.sample.com/long/url";
String shortURL = shortener.shortenURL(longURL);
System.out.println("Shortened URL: " + shortURL);
}
}

Scalability and Performance

By incorporating these scalability and performance strategies into the design and implementation of the URL shortening service, you can ensure that it can manage a large volume of requests efficiently, whereas, it maintains low latency and high availability.

Caching Mechanisms:

  • Employ caching mechanisms to diminish latency and database load. Use an in-memory caching solution, such as Redis or Memcached to store frequently accessed URLs and their mappings.
  • Implement a distributed caching system to ensure scalability and availability of cached data across multiple nodes. Consider using a caching-as-a-service solution like Amazon ElastiCache or Google Cloud Memorystore.

Memcached is an easy-to-use, high-performance, in-memory data store. It offers a mature, scalable, open-source solution for delivering sub-millisecond response times making it useful as a cache or session store. Memcached is a popular choice for powering real-time applications in Web, Mobile Apps, Gaming, Ad-Tech, and E-Commerce.

Load Balancing and Horizontal Scaling:

  • Deploy a load balancer to distribute incoming traffic across multiple instances of the URL shortening service. Use a cloud load balancer (e.g., AWS Elastic Load Balancing, Google Cloud Load Balancing) for automatic scaling and high availability.
  • Implement horizontal scaling by adding more server instances to the deployment as the demand for the service grows. Use containerization and orchestration tools, like Kubernetes or Docker Swarm to manage the scaling process automatically.

Asynchronous Processing:

  • Offload non-time-sensitive tasks, such as logging or analytics, to asynchronous processing queues. Use message brokers like Apache Kafka or RabbitMQ to decouple components and handle tasks asynchronously.
  • Implement Microservices Architecture to break down the URL shortening service in high levels into smaller, independently scalable components, each responsible for a specific task or functionality.

A message broker is software that enables applications, systems and services to communicate with each other and exchange information. The message broker does this by translating messages between formal messaging protocols. This allows interdependent services to “talk” with one another directly, even if they were written in different languages or implemented on different platforms.

Security Issues

  • Implement rate limiting mechanisms to restrict the number of requests from a single IP address or user account within a specific time period. This helps prevent abuse, spam, and DoS (Denial of Service) attacks.
  • Validate and sanitize user input to prevent injection attacks, such as SQL injection, XSS (Cross-Site Scripting), and command injection.
  • Integrate CAPTCHA (Completely Automated Public Turing test to tell Computers and Humans Apart) challenges for suspicious or high-frequency requests to ensure that the user is human and not a bot. CAPTCHA can be applied to verify user actions, like URL shortening or accessing restricted resources.

Rate limiting is a strategy for limiting network traffic. It puts a cap on how often someone can repeat an action within a certain timeframe — for instance, trying to log in to an account. Rate limiting can help stop certain kinds of malicious bot activity. It can also reduce strain on web servers.

  • Log security-related events, like failed login attempts, suspicious requests, and access control violations, to identify and investigate potential security incidents.

In Conclusion

As a matter of fact, URL shortening services serve as indispensable tools in simplifying lengthy and complex URLs into shorter, more manageable ones. Although the automatic link shortening features offered by social media platforms, URL shortening services remain invaluable, particularly in contexts where clickable links are unavailable, like business cards, print ads, or podcast interviews. This essay has explored the design and implementation of a URL shortening service, addressing various functional and non-functional requirements.

Continuous refinement and adaptation of the URL shortening service will be essential to keep pace with evolving user needs and technological advancements. By staying responsive to user feedback, integrating new features, and prioritizing security improvements, the URL shortening service can continue to serve as a valuable tool for simplifying URL management and improving communication efficiency in the digital age.

--

--

Kayvan Kaseb
Software Development

Senior Android Developer, Technical Writer, Researcher, Artist, Founder of PURE SOFTWARE YAZILIM LİMİTED ŞİRKETİ https://www.linkedin.com/in/kayvan-kaseb