Skip to content

Instantly share code, notes, and snippets.

@mayankchoubey
Last active October 22, 2023 06:00
Show Gist options
  • Save mayankchoubey/f2f21b9b417fe36cd318fe2707264b0a to your computer and use it in GitHub Desktop.
Save mayankchoubey/f2f21b9b417fe36cd318fe2707264b0a to your computer and use it in GitHub Desktop.
Spring Boot - URL shortener service in PostgreSQL
server.port=3000
spring.datasource.url= jdbc:postgresql://localhost:5432/${dbName}
spring.datasource.username= ${dbUser}
spring.datasource.password= ${dbUserPass}
spring.datasource.max-active=10
spring.datasource.max-idle=10
spring.datasource.min-idle=10
spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation= true
spring.jpa.properties.hibernate.dialect= org.hibernate.dialect.PostgreSQLDialect
# Hibernate ddl auto (create, create-drop, validate, update)
spring.jpa.hibernate.ddl-auto= update
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.1.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</name>
<description>URL shortener project for Spring Boot with Virtual threads</description>
<properties>
<java.version>21</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.aventrix.jnanoid</groupId>
<artifactId>jnanoid</artifactId>
<version>2.0.0</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>6.0.13</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<mainClass>com.example.shorten.UrlShortenerApplication</mainClass>
<layout>JAR</layout>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<release>21</release>
<enablePreview>true</enablePreview>
<compilerArgs>--enable-preview</compilerArgs>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.5.0</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.1.2</version>
<configuration>
<argLine>--enable-preview</argLine>
</configuration>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>native</id>
<build>
<plugins>
<plugin>
<groupId>org.graalvm.buildtools</groupId>
<artifactId>native-maven-plugin</artifactId>
<version>0.9.27</version>
<extensions>true</extensions>
<executions>
<execution>
<id>build-native</id>
<goals>
<goal>compile-no-fork</goal>
</goals>
<phase>package</phase>
</execution>
<execution>
<id>test-native</id>
<goals>
<goal>test</goal>
</goals>
<phase>test</phase>
</execution>
</executions>
<configuration>
<!-- ... -->
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>
package com.example.shorten;
import java.time.LocalDateTime;
import jakarta.persistence.Entity;
import jakarta.persistence.Table;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
@Entity
@Table(name = "shortenedurls")
public class ShortenedUrl {
@Id
private String id;
private String srcurl;
private LocalDateTime created;
private LocalDateTime lastaccessed;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getSrcUrl() {
return srcurl;
}
public void setSrcUrl(String srcUrl) {
this.srcurl = srcUrl;
}
public LocalDateTime getCreated() {
return created;
}
public void setCreated(LocalDateTime created) {
this.created = created;
}
public LocalDateTime getLastAccessed() {
return lastaccessed;
}
public void setLastAccessed(LocalDateTime lastAccessed) {
this.lastaccessed = lastAccessed;
}
}
package com.example.shorten;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.embedded.tomcat.TomcatProtocolHandlerCustomizer;
import org.springframework.context.annotation.Bean;
import java.util.concurrent.Executors;
@SpringBootApplication
public class UrlShortenerApplication {
public static void main(String[] args) {
SpringApplication.run(UrlShortenerApplication.class, args);
}
@Bean
public TomcatProtocolHandlerCustomizer<?> protocolHandlerVirtualThreadExecutorCustomizer() {
return protocolHandler -> {
protocolHandler.setExecutor(Executors.newVirtualThreadPerTaskExecutor());
};
}
}
package com.example.shorten;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.http.ResponseEntity;
import org.springframework.http.HttpStatus;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.RestController;
import java.util.Optional;
import javax.print.attribute.standard.MediaTray;
import com.example.shorten.UrlShortenerRequest;
import com.example.shorten.UrlShortenerResponse;
import com.example.shorten.UrlShortenerError;
import com.example.shorten.UrlShortenerService;
import org.springframework.beans.factory.annotation.Autowired;
@RestController
public class UrlShortenerController {
@Autowired
UrlShortenerService shortenerService;
@PostMapping(
value="/shorten",
consumes = MediaType.APPLICATION_JSON_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity handleRequest(@RequestBody UrlShortenerRequest shortenRequest) {
if(shortenRequest.getSrcUrl() == null) {
return ResponseEntity.badRequest()
.contentType(MediaType.APPLICATION_JSON)
.body(new UrlShortenerError("Parameter 'srcUrl' is missing"));
}
String srcUrl = shortenRequest.getSrcUrl();
if(srcUrl.length()>250) {
return ResponseEntity.badRequest()
.contentType(MediaType.APPLICATION_JSON)
.body(new UrlShortenerError("Parameter 'srcUrl' must not be more than 250 characters"));
}
if(!(srcUrl.startsWith("http://") || srcUrl.startsWith("https://"))) {
return ResponseEntity.badRequest()
.contentType(MediaType.APPLICATION_JSON)
.body(new UrlShortenerError("Parameter 'srcUrl' must start with http:// or https://"));
}
String shortenedUrl = shortenerService.shorten(srcUrl);
if(shortenedUrl.isEmpty()) {
return ResponseEntity.badRequest()
.contentType(MediaType.APPLICATION_JSON)
.body(new UrlShortenerError("Failed to shorten"));
}
UrlShortenerResponse resp = new UrlShortenerResponse(srcUrl, shortenedUrl);
return ResponseEntity.ok()
.contentType(MediaType.APPLICATION_JSON)
.body(resp);
}
}
package com.example.shorten;
public class UrlShortenerError {
private String errMsg;
public UrlShortenerError() {
}
public UrlShortenerError(String errMsg) {
this.errMsg = errMsg;
}
public String getErrMsg() {
return this.errMsg;
}
public void setErrMsg(String errMsg) {
this.errMsg = errMsg;
}
}
package com.example.shorten;
import org.springframework.data.jpa.repository.JpaRepository;
import com.example.shorten.ShortenedUrl;
public interface UrlShortenerRepository extends JpaRepository<ShortenedUrl, String> {
}
package com.example.shorten;
public class UrlShortenerRequest {
private String srcUrl;
public String getSrcUrl() {
return this.srcUrl;
}
public void setSrcUrl(String srcUrl) {
this.srcUrl = srcUrl;
}
}
package com.example.shorten;
public class UrlShortenerResponse {
private String srcUrl;
private String shortenedUrl;
public UrlShortenerResponse() {
}
public UrlShortenerResponse(String srcUrl, String shortenedUrl) {
this.srcUrl = srcUrl;
this.shortenedUrl = shortenedUrl;
}
public String getSrcUrl() {
return this.srcUrl;
}
public void setSrcUrl(String srcUrl) {
this.srcUrl = srcUrl;
}
public String getShortenedUrl() {
return this.shortenedUrl;
}
public void setShortenedUrl(String shortenedUrl) {
this.shortenedUrl = shortenedUrl;
}
}
package com.example.shorten;
import java.time.LocalDateTime;
import com.aventrix.jnanoid.jnanoid.*;
import com.example.shorten.ShortenedUrl;
import com.example.shorten.UrlShortenerRepository;
import org.springframework.stereotype.Service;
import org.springframework.beans.factory.annotation.Autowired;
@Service
public class UrlShortenerService {
final String baseUrl = "http://test.short/";
@Autowired
UrlShortenerRepository shortenerRepository;
public String shorten(String srcUrl) {
if(srcUrl.isEmpty()) {
return "";
}
String urlId = NanoIdUtils.randomNanoId(
NanoIdUtils.DEFAULT_NUMBER_GENERATOR,
NanoIdUtils.DEFAULT_ALPHABET,
10
);
String shortUrl = baseUrl + urlId;
ShortenedUrl shortenedUrl = new ShortenedUrl();
shortenedUrl.setId(urlId);
shortenedUrl.setSrcUrl(srcUrl);
shortenedUrl.setCreated(LocalDateTime.now());
shortenedUrl.setLastAccessed(LocalDateTime.now());
ShortenedUrl savedData = shortenerRepository.save(shortenedUrl);
return savedData.getId().isEmpty() ? "" : shortUrl;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment