Jul 2023

Building a User-Friendly URL Shortener Using Spring Boot, Postgres, and FL0

Subscribe to learn about new product features, the latest in technology, solutions, and updates.

Spring Boot





In this tutorial, we will create a user-friendly URL shortener using SpringBoot and Postgres, deployed on FL0 for easy management and control. 🔗✨


Nowadays, URLs have become an essential part of our workflow. However, long and complex URLs can be difficult to share or work with.

In this tutorial, we will build a simple-to-use URL shortener that provides a convenient way to transform long URLs into shorter, more manageable links in just one click! 🧑‍💻

We would be using Spring Boot to build our backend, Postgres as our database, both deployed simply using FL0. Then we would go ahead and build the user interface in the form of a simple Chrome Extension, which could call our APIs!

Here’s some internet humor before we get started:


Before we dive into the code, let’s take a moment to understand the high-level overview of our project.

Our URL shortener will be a Chrome extension designed to provide a seamless and delightful experience for users. Let’s walk through the user journey to get a clear picture:

  1. Navigation: The user opens Chrome and visits the webpage he/she wants to shorten using our installed extension.
  2. URL Shortening: Upon clicking the extension, it fetches the active tab’s URL and sends it to our shortening service. This service is a Spring Boot application hosted on FL0, which processes the URL and generates a unique path.
  3. URL Construction and Copying: The extension takes this unique path, constructs the full shortened URL on the front end, displays it to the user, and it can be copied with a single click.

Here’s a high-level diagram for better understanding:

Step 0: Setting Up the Spring Boot Project

Before we begin, we need to make sure we have the following tools installed:

  • Code Editor: In this tutorial, we’ll be using IntelliJ IDEA.
  • JDK 17: we need to have JDK 17 installed.
  • Docker Desktop (on Mac/Windows): We’ll utilize Docker to containerize our application and simplify deployment.

Now we would go ahead and set up our new Spring Boot project using Spring Initializr 🌱

  1. Let’s visit This is the Spring Initializr, a web-based tool that helps in creating Spring Boot projects quickly.
  2. Now, we would need to configure our settings as follows:

Project Metadata: Specify the project metadata such as group, artifact, and version.

Dependencies: Add the following dependencies:

  • !!Spring Web!!: To build RESTful APIs and handle HTTP requests.
  • !!Spring Data JPA!!: For working with databases using Object-Relational Mapping (ORM).
  • !!PostgreSQL Driver!!: To connect our application with a PostgreSQL database.
  • !!Lombok!!: A library that simplifies Java code by reducing boilerplate.

3. We will select the latest stable Java version (Java 17), choose the project packaging as JAR, and select !!Gradle!! as the build tool. 🧑💻

4. Click on the “Generate” button to download a zip file containing the project structure and necessary files 🗂️ as shown👇

Spring Initializr

Setting Up the Project

  1. We will extract the downloaded zip file to a preferred location.
  2. Now we will open our code editor (IntelliJ IDEA, in our case) and import the project by selecting the extracted folder as the project directory.
  3. Once the project is loaded, we would need to download the project dependencies specified in the !!build.gradle!! file. This can be done automatically in IntelliJ IDEA by clicking on the Gradle toolbar located on the right side of the editor and selecting the "Refresh" button.
  4. Verify that the dependencies are successfully downloaded by checking the Gradle Console for any errors or warnings.

Step 1: Database and Config

In this step, we’ll configure the necessary files and set up the database connection for our user-friendly URL shortener. Let’s get started with the configuration setup!

Postgresql Database Setup

To run our app locally, we would also need !!postgresql!! running. So, lets set it up quickly. We create a new folder !!src/main/resources/local!! and create a new file !!local-postgre-docker-compose.yml!!

version: '3'

    image: postgres
    restart: always
      - 5432:5432
      POSTGRES_DB: url_shortener
      POSTGRES_USER: user123
      POSTGRES_PASSWORD: pass123
      - db_data:/var/lib/postgresql/data

To start our DB, in terminal navigate to the folder containing this file and run the command:

docker-compose -f local-postgre-docker-compose.yml up -d

This will start !!postgresql!! database as a !!docker container!! and we will be able to connect to it on !!localhost:5432!! using the credentials as mentioned in the above docker file

Configuring Files and Database Connection

  1. Open the !!application.yml!! file located in the !!src/main/resources!! directory. This file allows us to define properties for our application.
  2. Add the following properties:
  port: 8080

    url: jdbc:postgresql://${DB_HOST_NAME:localhost}:${DB_PORT:5432}/${DB_NAME:url_shortener}
    username: ${DB_USERNAME:user123}
    password: ${DB_PASSWORD:pass123}
      ddl-auto: update

  allowed-characters: ${ALLOWED_CHARS:abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789}
  key-length: ${KEY_LENGTH:6}
  • The variables written as !!${ENV_VARIABLE_NAME:default_value}!! can be set using environment variables while deployment.
  • For local development we can use the default values.
  • !!server.port!!: The port on which our application will run.
  • !!spring.datasource.url!!: The URL for connecting to our PostgreSQL database.
  • !!spring.datasource.username!! and !!spring.datasource.password!!: PostgreSQL database credentials.
  • !!short-url.allowed-characters!!: The characters allowed in the generated keys. Feel free to modify or expand the character set if desired.
  • !!short-url.key-length!!: The length of the generated keys for short url. We will be using length of 6 characters as key.

Now we can run our application using below command:

./gradlew bootRun

ShortUrlConfig Class

To centralize our configuration properties and make them easily accessible, let’s create a !!ShortUrlConfig!! <code>ShortUrlConfig</code> class. This class will be annotated with !!@ConfigurationProperties(prefix="short-url")!! to bind the properties from the !!application.yml!! file to the corresponding fields in our class. Here's an example:

package com.fl0.urlshortener.config;

import lombok.Getter;
import lombok.Setter;
@ConfigurationProperties(prefix = "short-url")
public class ShortUrlConfig {
    private String allowedCharacters;
    private int keyLength;

This will require us to add !!@ConfigurationPropertiesScan!! on top of the main application class. So, lets add it to our !!UrlshortenerApplication!! :

package com.fl0.urlshortener;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
public class UrlshortenerApplication {
 public static void main(String[] args) {, args);

Now that we have our files configured and the database connection established, it’s time to move on to creating the necessary models and repositories.

Step 2: Creating Entity and Repository

Next we’ll define and create the necessary models and repositories for our URL shortener. The models will represent the table structure of our shortened URLs, and the repositories will handle the database operations. Let’s dive in!

ShortUrl Entity

  1. Create a new Java class named !!ShortUrlEntity!! in a new package !!com.fl0.urlshortener.entity!!
  2. Define the fields for the !!ShortUrlEntity!! entity class:
package com.fl0.urlshortener.entity;

import jakarta.persistence.*;
import lombok.*;

@Table(name = "urls")
public class ShortUrlEntity {
    @Column(name = "id", nullable = false)
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(unique = true)
    private String key;

    @Column(nullable = false, columnDefinition = "TEXT")
    private String fullUrl;

    @Column(nullable = false)
    private Long clickCount;
  1. The !!ShortUrlEntity!! entity represents a shortened URL and is annotated with @Entity to indicate that it's a JPA entity. The @Table annotation specifies the name of the table in the database.
  2. The entity has the following fields:
  • !!id!!: The primary key generated automatically by the database.
  • !!key!!: The unique key representing the shortened URL.
  • !!fullUrl!!: The original full URL that was shortened.
  • !!!clickCount!!: The number of times the shortened URL has been clicked.


  1. Create a new Java interface named !!ShortUrlRepository!! in a new package !!com.fl0.urlshortener.repository!!
  2. Extend the !!JpaRepository!! interface and define custom methods for the repository:
package com.fl0.urlshortener.repository;

import com.fl0.urlshortener.entity.ShortUrlEntity;

public interface ShortUrlRepository extends JpaRepository {
    ShortUrlEntity findByKey(String key);
    ShortUrlEntity findByFullUrl(String fullUrl);
  1. The !!ShortUrlRepository!! extends the !!JpaRepository!! interface provided by Spring Data JPA. It enables us to perform CRUD operations on the !!ShortUrlEntity!! easily.
  2. We will also define two custom methods:
  • !!findByKey!!: Retrieves a !!ShortUrlEntity!! entity based on the given key.
  • !!findByFullUrl:!! Retrieves a !!ShortUrlEntity!! entity based on the given full URL.

These methods will be used in our service layer to retrieve and manipulate data.

Step 3: Creating DTOs, Utility and Service classes

Now we’ll create the data transfer objects (DTOs), a utility class and service layer. The DTOs will facilitate data transfer, the utility class will provide helpful methods and the service will handle the business logic. Let’s proceed!

DTOs (Data Transfer Objects)

  1. Let’s create a new Java class named !!ShortUrlRequest!! in the !!com.example.urlshortener.dto!! package.
  2. Now we will define the fields for the !!ShortUrlRequest!! class:
package com.fl0.urlshortener.dto;

import lombok.Getter;
import lombok.Setter;

public class ShortUrlRequest {
    private String url;
  1. The !!ShortUrlRequest!! DTO represents a request to create a shortened URL. It has a single field, !!url!!.
  2. We will create another Java class named !!ShortUrlResponse!! in the same package.
  3. Now we will define the fields for the !!ShortUrlResponse!! class:
package com.fl0.urlshortener.dto;

import lombok.Builder;
import lombok.Getter;
import lombok.Setter;

public class ShortUrlResponse {
    private String key;
  1. The !!ShortUrlResponse!! DTO represents the response after creating a shortened URL. It has a single field: !!key!!, which holds the unique key as a response.


We will create a new Java class named !!ShortUrlUtil!! in the !!com.fl0.urlshortener.util!! package and add the !!@Component!! annotation to the !!ShortUrlUtil!! class to make it a Spring bean:

package com.fl0.urlshortener.util;

import com.fl0.urlshortener.config.ShortUrlConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.Random;

public class ShortUrlUtil {

    private final ShortUrlConfig config;

    public ShortUrlUtil(ShortUrlConfig config) {
        this.config = config;

    public String generateUniqueKey() {
        int keyLength = config.getKeyLength();
        String allowedCharacters = config.getAllowedCharacters();

        StringBuilder keyBuilder = new StringBuilder();
        Random random = new Random();

        for (int i = 0; i < keyLength; i++) {
            int randomIndex = random.nextInt(allowedCharacters.length());

        return keyBuilder.toString();

The !!ShortUrlUtil!! class is annotated with !!@Component!! to make it a Spring bean. It is also injected with the !!ShortUrlConfig!! using constructor injection.

The !!ShortUrlUtil!! class provides a single method:

  • !!generateUniqueKey()!!: Generates a unique key based on the specified length and allowed characters from the configuration.

This method will be used in the service layer.


  1. Create a new Java class named !!UrlShortenerService!! in the !!com.fl0.urlshortener.service!! package.
  2. Add the following methods to handle URL shortening and retrieval:
package com.fl0.urlshortener.service;

import com.fl0.urlshortener.dto.ShortUrlRequest;
import com.fl0.urlshortener.dto.ShortUrlResponse;
import com.fl0.urlshortener.entity.ShortUrlEntity;
import com.fl0.urlshortener.repository.ShortUrlRepository;
import com.fl0.urlshortener.util.ShortUrlUtil;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.web.servlet.view.RedirectView;

public class UrlShortenerService {

    private final ShortUrlRepository repository;
    private final ShortUrlUtil util;

    public ShortUrlResponse createShortUrl(ShortUrlRequest request) {
        String fullUrl = request.getUrl();

        ShortUrlEntity existingShortUrl = repository.findByFullUrl(fullUrl);

        if (existingShortUrl != null) {
            return ShortUrlResponse.builder().key(existingShortUrl.getKey()).build();
        } else {
            String newKey = util.generateUniqueKey();
            ShortUrlEntity newEntity = ShortUrlEntity.builder()
            return ShortUrlResponse.builder().key(newKey).build();

    public RedirectView getFullUrl(String key) {
        ShortUrlEntity entityInDb = repository.findByKey(key);
        entityInDb.setClickCount(entityInDb.getClickCount() + 1);;
        return new RedirectView(entityInDb.getFullUrl());

The !!UrlShortenerService!! class handles the business logic of URL shortening and retrieval. It relies on the !!ShortUrlRepository!! for database operations and the !!ShortUrlUtil!! for generating unique keys.

The !!createShortUrl!! method checks if the URL already exists in the database.

If it exists, we get the key (the path) of the URL from the database.

Otherwise, it generates a new key, saves it along with the URL in the database, and returns the newly generated key.

The !!getFullUrl!! method retrieves the original full URL based on the provided key.

It finds the key in the database, increments the click count, saves the changes, and redirects to the original full URL.

Our application is now equipped with the necessary components to handle the creation and retrieval of shortened URLs.

Step 5: Enabling Cross-Origin Resource Sharing (CORS)

To allow requests from the Chrome extension to our backend API, we need to configure Cross-Origin Resource Sharing (CORS). In this step, we’ll create a CorsConfig class in our Spring Boot application to handle CORS configuration.

  1. We create a new Java class named !!!!CorsConfig in the !!com.fl0.urlshortener.config!! package.
package com.fl0.urlshortener.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

public class CorsConfig implements WebMvcConfigurer {

    public void addCorsMappings(CorsRegistry registry) {
                .allowedMethods("GET", "POST");
  1. The !!CorsConfig!! class implements the !!WebMvcConfigurer!! interface, allowing us to customize the CORS configuration for our application.
  2. With this configuration, our backend will allow requests from the Chrome extension, enabling communication between the extension and the API.
  3. Save the !!CorsConfig!! class.

We have successfully built the !!CorsConfig!! class to handle CORS configuration in our Spring Boot application. This will ensure that our backend API can accept requests from our Chrome extension without any CORS-related issues. ✅

Next, let’s move on to build our Chrome extension. 👨🏻‍💻

Step 6: Building the Chrome Extension

In this step, we’ll create a custom Chrome extension for our URL shortener. The extension will provide a convenient way for users to shorten URLs directly from their browser. Let’s dive into the world of Chrome extension development!

  1. We will create a new project named !!url-shortener-extension!!.


  "manifest_version": 3,
  "name": "URL Shortener",
  "version": "1.0",
  "description": "Shortens URLs with ease!",
  "background": {
    "service_worker": "background.js"
  "action": {
    "default_popup": "popup.html",
    "default_icon": "icon.png"
  "permissions": ["activeTab"],
  "icons": {
    "16": "icon.png",
    "48": "icon.png",
    "128": "icon.png"




body {
    background-color: #e6f7ff;
    margin: 5px;
    font-family: Arial, sans-serif;

#copyBtn {
    padding: 5px 10px;
    background-color: #1890ff;
    border: none;
    color: white;
    font-size: 1.2em;
    transition: background-color 0.5s ease;
    text-align: center;
    white-space: nowrap;
    text-overflow: ellipsis;
    cursor: pointer;


let shortenedUrl;

window.onload = function() {
    chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
            {message: 'fetchUrl', url: tabs[0].url},
            function(response) {
                shortenedUrl = response.url;
                document.getElementById('copyBtn').disabled = false;
document.getElementById('copyBtn').addEventListener('click', function() {
    navigator.clipboard.writeText(shortenedUrl).then(function() {
        console.log('Copying to clipboard was successful!');
        const btn = document.getElementById('copyBtn');
        btn.innerText = 'Success!'; = '#52c41a';
        // Close the popup after 2 seconds
        setTimeout(window.close, 2000);
    }, function(err) {
        console.error('Could not copy text: ', err);


const backendUrl = 'https://localhost:8080';
// Don't forget to replace this url with the actual backend url after deployment

chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
    if (request.message === 'fetchUrl') {
        fetch(backendUrl + 'createUrl', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json'
            body: JSON.stringify({ url: request.url })
            .then(response => response.json())
            .then(data => {
                sendResponse({url: backendUrl + data.key});
            .catch(err => console.error('Error: ', err));
        return true;

Now lets load the extension in our Chrome browser by following these steps:

  1. Open the Chrome browser.
  2. Go to !!chrome://extensions/!!.
  3. Enable the “Developer mode” toggle on the top right corner.
  4. Click on the “Load unpacked” button.
  5. Select the !!url-shortener-extension!! directory.
  6. The extension will be loaded and available in the Chrome browser.

We’ve successfully built the Chrome extension for our URL shortener.

Now let’s go ahead and dockerize our application.

Step 7: Dockerizing the App

Now we’ll dockerize our application, making it easy to deploy and run in a containerized environment. Let’s get started!


We create a new file named !!Dockerfile!! in the root directory of our project.

Here we specify the instructions for building the Docker image of our app for deployment.

# Start with a base image containing Java runtime (AdoptOpenJDK)
FROM openjdk:17-jdk-slim AS build

# Set the working directory in the image to "/app"

# Copy the Gradle executable to the image
COPY gradlew ./

# Copy the 'gradle' folder to the image
COPY gradle ./gradle

# Give permission to execute the gradle script
RUN chmod +x ./gradlew

# Copy the rest of the application source code
COPY . .

# Use Gradle to build the application
RUN sh ./gradlew build

# Set up a second stage, which will only keep the compiled application and not the build tools and source code
FROM openjdk:17-jdk-slim

# Set the working directory to '/app'

# Copy the jar file from the first stage
COPY --from=build /app/build/libs/*.jar app.jar

# Set the startup command to execute the jar
CMD ["java", "-jar", "/app/app.jar"]

Docker Compose

Now we will create a !!docker-compose!! in the project’s root directory.

version: '3.8'
      context: .
      dockerfile: Dockerfile
      - "8080:8080"

This !!docker-compose.yml!! file defines the service !!url-shortener-backend!!. The service is based on the configuration in the !!Dockerfile!!. It maps the container's port 8080 to the host's port 8080.

Let’s now navigate to the next section and explore hosting our backend and database with FL0!

Step 8: Hosting our Backend and DB with FL0

Now we would go ahead and host our application with the help of FL0

Setting Up FL0 Database

  1. In the FL0 dashboard, we would need to create a new project.
  2. We need to click on “Create a Postgres database”

Once the database is created, FL0 will provide us with the necessary database connection details, including the connection URL, username, and password.

4. Add Database Connection Credentials as Environment Variables in !!fl0-url-shortener-backend!!

And…Done ✅

Our project is hosted on FL0 successfully.


In this tutorial successfully built a link shortener chrome extension along with it’s backend and database and hosted it using FL0. 🎉

You may find the repository link here

Moreover, here’s the link for the Chrome Extension if you want to use it yourself

You may visit to start building and deploying your applications! 🚀

Dale Brett
Founder & CEO, FL0

Copy link

Our blog

Latest blog posts

Tool and strategies modern teams need to help their companies grow.

View all posts

View all posts

ready to ship

We’re excited to see you launch your next big idea.

Get started for free

arrow right