A very performant and light (2mb in memory) link shortener and tracker. Written in Rust and React and uses Postgres/SQLite.

bundle frontend too

+3
.env.example
···
··· 1 + DATABASE_URL=postgresql://user:password@localhost/dbname 2 + SERVER_HOST=127.0.0.1 3 + SERVER_PORT=8080
+5 -2
.gitignore
··· 2 **/node_modules 3 node_modules 4 .env 5 - .env.* 6 - /static
··· 2 **/node_modules 3 node_modules 4 .env 5 + /static 6 + /target 7 + /release 8 + release.tar.gz 9 + *.log
+29 -6
Dockerfile
··· 1 - # Build stage 2 - FROM rust:latest as builder 3 4 # Install PostgreSQL client libraries and SSL dependencies 5 RUN apt-get update && \ ··· 16 COPY migrations/ migrations/ 17 COPY .sqlx/ .sqlx/ 18 19 - # Build your application 20 RUN cargo build --release 21 22 # Runtime stage ··· 30 WORKDIR /app 31 32 # Copy the binary from builder 33 - COPY --from=builder /usr/src/app/target/release/simplelink /app/simplelink 34 # Copy migrations folder for SQLx 35 - COPY --from=builder /usr/src/app/migrations /app/migrations 36 37 # Expose the port (this is just documentation) 38 EXPOSE 8080 ··· 42 ENV SERVER_PORT=8080 43 44 # Run the binary 45 - CMD ["./simplelink"]
··· 1 + # Frontend build stage 2 + FROM oven/bun:latest AS frontend-builder 3 + 4 + WORKDIR /usr/src/frontend 5 + 6 + # Copy frontend files 7 + COPY frontend/package*.json ./ 8 + RUN bun install 9 + 10 + COPY frontend/ ./ 11 + 12 + # Build frontend with production configuration 13 + ARG API_URL=http://localhost:8080 14 + ENV VITE_API_URL=${API_URL} 15 + RUN bun run build 16 + 17 + # Rust build stage 18 + FROM rust:latest AS backend-builder 19 20 # Install PostgreSQL client libraries and SSL dependencies 21 RUN apt-get update && \ ··· 32 COPY migrations/ migrations/ 33 COPY .sqlx/ .sqlx/ 34 35 + # Create static directory and copy frontend build 36 + COPY --from=frontend-builder /usr/src/frontend/dist/ static/ 37 + 38 + # Build the application 39 RUN cargo build --release 40 41 # Runtime stage ··· 49 WORKDIR /app 50 51 # Copy the binary from builder 52 + COPY --from=backend-builder /usr/src/app/target/release/simplelink /app/simplelink 53 + 54 # Copy migrations folder for SQLx 55 + COPY --from=backend-builder /usr/src/app/migrations /app/migrations 56 + 57 + # Copy static files 58 + COPY --from=backend-builder /usr/src/app/static /app/static 59 60 # Expose the port (this is just documentation) 61 EXPOSE 8080 ··· 65 ENV SERVER_PORT=8080 66 67 # Run the binary 68 + CMD ["./simplelink"]
+87
build.sh
···
··· 1 + #!/bin/bash 2 + 3 + # Default values 4 + API_URL="http://localhost:8080" 5 + RELEASE_MODE=false 6 + 7 + # Parse command line arguments 8 + for arg in "$@" 9 + do 10 + case $arg in 11 + api-domain=*) 12 + API_URL="${arg#*=}" 13 + shift 14 + ;; 15 + --release) 16 + RELEASE_MODE=true 17 + shift 18 + ;; 19 + esac 20 + done 21 + 22 + echo "Building project with API_URL: $API_URL" 23 + echo "Release mode: $RELEASE_MODE" 24 + 25 + # Check if cargo is installed 26 + if ! command -v cargo &> /dev/null; then 27 + echo "cargo is not installed. Please install Rust and cargo first." 28 + exit 1 29 + fi 30 + 31 + # Check if npm is installed 32 + if ! command -v npm &> /dev/null; then 33 + echo "npm is not installed. Please install Node.js and npm first." 34 + exit 1 35 + fi 36 + 37 + # Build frontend 38 + echo "Building frontend..." 39 + # Create .env file for Vite 40 + echo "VITE_API_URL=$API_URL" > frontend/.env 41 + 42 + # Install frontend dependencies and build 43 + cd frontend 44 + npm install 45 + npm run build 46 + cd .. 47 + 48 + # Create static directory if it doesn't exist 49 + mkdir -p static 50 + 51 + # Clean existing static files 52 + rm -rf static/* 53 + 54 + # Copy built files to static directory 55 + cp -r frontend/dist/* static/ 56 + 57 + # Build Rust project 58 + echo "Building Rust project..." 59 + if [ "$RELEASE_MODE" = true ]; then 60 + cargo build --release 61 + 62 + # Create release directory 63 + mkdir -p release 64 + 65 + # Copy binary and static files to release directory 66 + cp target/release/simplelink release/ 67 + cp -r static release/ 68 + cp .env.example release/.env 69 + 70 + # Create a tar archive 71 + tar -czf release.tar.gz release/ 72 + 73 + echo "Release archive created: release.tar.gz" 74 + else 75 + cargo build 76 + fi 77 + 78 + echo "Build complete!" 79 + echo "To run the project:" 80 + if [ "$RELEASE_MODE" = true ]; then 81 + echo "1. Extract release.tar.gz" 82 + echo "2. Configure .env file" 83 + echo "3. Run ./simplelink" 84 + else 85 + echo "1. Configure .env file" 86 + echo "2. Run 'cargo run'" 87 + fi
+36 -1
docker-compose.yml
··· 16 interval: 5s 17 timeout: 5s 18 retries: 5 19 20 volumes: 21 shortener-data: 22 -
··· 16 interval: 5s 17 timeout: 5s 18 retries: 5 19 + networks: 20 + - shortener-network 21 + 22 + app: 23 + build: 24 + context: . 25 + dockerfile: Dockerfile 26 + args: 27 + - API_URL=${API_URL:-http://localhost:8080} 28 + container_name: shortener-app 29 + ports: 30 + - "8080:8080" 31 + environment: 32 + - DATABASE_URL=postgresql://shortener:shortener123@db:5432/shortener 33 + - SERVER_HOST=0.0.0.0 34 + - SERVER_PORT=8080 35 + depends_on: 36 + db: 37 + condition: service_healthy 38 + healthcheck: 39 + test: ["CMD", "curl", "-f", "http://localhost:8080/api/health"] 40 + interval: 30s 41 + timeout: 10s 42 + retries: 3 43 + start_period: 40s 44 + networks: 45 + - shortener-network 46 + deploy: 47 + restart_policy: 48 + condition: on-failure 49 + max_attempts: 3 50 + window: 120s 51 + 52 + networks: 53 + shortener-network: 54 + driver: bridge 55 56 volumes: 57 shortener-data:
+2
src/main.rs
··· 1 use actix_cors::Cors; 2 use actix_web::{web, App, HttpServer}; 3 use anyhow::Result; 4 use simplelink::{handlers, AppState}; ··· 61 .route("/health", web::get().to(handlers::health_check)), 62 ) 63 .service(web::resource("/{short_code}").route(web::get().to(handlers::redirect_to_url))) 64 }) 65 .workers(2) 66 .backlog(10_000)
··· 1 use actix_cors::Cors; 2 + use actix_files as fs; 3 use actix_web::{web, App, HttpServer}; 4 use anyhow::Result; 5 use simplelink::{handlers, AppState}; ··· 62 .route("/health", web::get().to(handlers::health_check)), 63 ) 64 .service(web::resource("/{short_code}").route(web::get().to(handlers::redirect_to_url))) 65 + .service(fs::Files::new("/", "./static").index_file("index.html")) 66 }) 67 .workers(2) 68 .backlog(10_000)