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
···
+
DATABASE_URL=postgresql://user:password@localhost/dbname
+
SERVER_HOST=127.0.0.1
+
SERVER_PORT=8080
+5 -2
.gitignore
···
**/node_modules
node_modules
.env
-
.env.*
-
/static
+
/static
+
/target
+
/release
+
release.tar.gz
+
*.log
+29 -6
Dockerfile
···
-
# Build stage
-
FROM rust:latest as builder
+
# Frontend build stage
+
FROM oven/bun:latest AS frontend-builder
+
+
WORKDIR /usr/src/frontend
+
+
# Copy frontend files
+
COPY frontend/package*.json ./
+
RUN bun install
+
+
COPY frontend/ ./
+
+
# Build frontend with production configuration
+
ARG API_URL=http://localhost:8080
+
ENV VITE_API_URL=${API_URL}
+
RUN bun run build
+
+
# Rust build stage
+
FROM rust:latest AS backend-builder
# Install PostgreSQL client libraries and SSL dependencies
RUN apt-get update && \
···
COPY migrations/ migrations/
COPY .sqlx/ .sqlx/
-
# Build your application
+
# Create static directory and copy frontend build
+
COPY --from=frontend-builder /usr/src/frontend/dist/ static/
+
+
# Build the application
RUN cargo build --release
# Runtime stage
···
WORKDIR /app
# Copy the binary from builder
-
COPY --from=builder /usr/src/app/target/release/simplelink /app/simplelink
+
COPY --from=backend-builder /usr/src/app/target/release/simplelink /app/simplelink
+
# Copy migrations folder for SQLx
-
COPY --from=builder /usr/src/app/migrations /app/migrations
+
COPY --from=backend-builder /usr/src/app/migrations /app/migrations
+
+
# Copy static files
+
COPY --from=backend-builder /usr/src/app/static /app/static
# Expose the port (this is just documentation)
EXPOSE 8080
···
ENV SERVER_PORT=8080
# Run the binary
-
CMD ["./simplelink"]
+
CMD ["./simplelink"]
+87
build.sh
···
+
#!/bin/bash
+
+
# Default values
+
API_URL="http://localhost:8080"
+
RELEASE_MODE=false
+
+
# Parse command line arguments
+
for arg in "$@"
+
do
+
case $arg in
+
api-domain=*)
+
API_URL="${arg#*=}"
+
shift
+
;;
+
--release)
+
RELEASE_MODE=true
+
shift
+
;;
+
esac
+
done
+
+
echo "Building project with API_URL: $API_URL"
+
echo "Release mode: $RELEASE_MODE"
+
+
# Check if cargo is installed
+
if ! command -v cargo &> /dev/null; then
+
echo "cargo is not installed. Please install Rust and cargo first."
+
exit 1
+
fi
+
+
# Check if npm is installed
+
if ! command -v npm &> /dev/null; then
+
echo "npm is not installed. Please install Node.js and npm first."
+
exit 1
+
fi
+
+
# Build frontend
+
echo "Building frontend..."
+
# Create .env file for Vite
+
echo "VITE_API_URL=$API_URL" > frontend/.env
+
+
# Install frontend dependencies and build
+
cd frontend
+
npm install
+
npm run build
+
cd ..
+
+
# Create static directory if it doesn't exist
+
mkdir -p static
+
+
# Clean existing static files
+
rm -rf static/*
+
+
# Copy built files to static directory
+
cp -r frontend/dist/* static/
+
+
# Build Rust project
+
echo "Building Rust project..."
+
if [ "$RELEASE_MODE" = true ]; then
+
cargo build --release
+
+
# Create release directory
+
mkdir -p release
+
+
# Copy binary and static files to release directory
+
cp target/release/simplelink release/
+
cp -r static release/
+
cp .env.example release/.env
+
+
# Create a tar archive
+
tar -czf release.tar.gz release/
+
+
echo "Release archive created: release.tar.gz"
+
else
+
cargo build
+
fi
+
+
echo "Build complete!"
+
echo "To run the project:"
+
if [ "$RELEASE_MODE" = true ]; then
+
echo "1. Extract release.tar.gz"
+
echo "2. Configure .env file"
+
echo "3. Run ./simplelink"
+
else
+
echo "1. Configure .env file"
+
echo "2. Run 'cargo run'"
+
fi
+36 -1
docker-compose.yml
···
interval: 5s
timeout: 5s
retries: 5
+
networks:
+
- shortener-network
+
+
app:
+
build:
+
context: .
+
dockerfile: Dockerfile
+
args:
+
- API_URL=${API_URL:-http://localhost:8080}
+
container_name: shortener-app
+
ports:
+
- "8080:8080"
+
environment:
+
- DATABASE_URL=postgresql://shortener:shortener123@db:5432/shortener
+
- SERVER_HOST=0.0.0.0
+
- SERVER_PORT=8080
+
depends_on:
+
db:
+
condition: service_healthy
+
healthcheck:
+
test: ["CMD", "curl", "-f", "http://localhost:8080/api/health"]
+
interval: 30s
+
timeout: 10s
+
retries: 3
+
start_period: 40s
+
networks:
+
- shortener-network
+
deploy:
+
restart_policy:
+
condition: on-failure
+
max_attempts: 3
+
window: 120s
+
+
networks:
+
shortener-network:
+
driver: bridge
volumes:
shortener-data:
-
+2
src/main.rs
···
use actix_cors::Cors;
+
use actix_files as fs;
use actix_web::{web, App, HttpServer};
use anyhow::Result;
use simplelink::{handlers, AppState};
···
.route("/health", web::get().to(handlers::health_check)),
)
.service(web::resource("/{short_code}").route(web::get().to(handlers::redirect_to_url)))
+
.service(fs::Files::new("/", "./static").index_file("index.html"))
})
.workers(2)
.backlog(10_000)