Fast & Secure Link Shortener
Shorten Your Links in Seconds
Create short, memorable links instantly. Save links to your account, track total clicks, and manage your custom destinations.
Your Shortened Link:
Ready
https://67er.link/
Direct Short Code Lookup
Type any 5-character code to resolve its destination URL directly.
My Shortened Links
Manage and inspect all shortened URLs registered under your account.
| Short Code | Original Destination | Total Clicks | Actions |
|---|
System Administration
Overview of registered accounts and user short links stored in MySQL.
Backend Deployment Files
Complete server configuration, database schema, and REST handler for self-hosting.
.htaccess (Apache Rewrite Rules)
RewriteEngine On
RewriteBase /
# Serve physical files and directories directly
RewriteCond %{REQUEST_FILENAME} -f [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^ - [L]
# Route SPA virtual application routes directly to index.html
RewriteRule ^(links|admin|code|login|signup)/?$ index.html [L,QSA]
# Route 5-character short codes through api.php lookup handler
RewriteRule ^([a-zA-Z0-0]{5})$ api.php?action=lookup&code=$1 [L,QSA]
schema.sql (MySQL Database Setup)
CREATE DATABASE IF NOT EXISTS `erlink_link` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE `erlink_link`;
CREATE TABLE IF NOT EXISTS `users` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`email` VARCHAR(100) UNIQUE NOT NULL,
`password` VARCHAR(255) NOT NULL,
`role` ENUM('user', 'admin') DEFAULT 'user',
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS `links` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`short_code` VARCHAR(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci UNIQUE NOT NULL,
`original_url` TEXT NOT NULL,
`user_id` INT NULL,
`clicks` INT DEFAULT 0,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Migration query if adding password column to existing users table:
-- ALTER TABLE `users` ADD COLUMN `password` VARCHAR(255) NOT NULL AFTER `email`;
api.php (Complete Backend REST Handler)
<?php
header('Content-Type: application/json');
header('X-Content-Type-Options: nosniff');
header('X-Frame-Options: SAMEORIGIN');
$db_host = 'localhost';
$db_name = 'erlink_link';
$db_user = 'erlink_link';
$db_pass = 'jejdj765$+$HeHD';
try {
$pdo = new PDO("mysql:host=$db_host;dbname=$db_name;charset=utf8mb4", $db_user, $db_pass, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false
]);
} catch (PDOException $e) {
echo json_encode(['error' => true, 'message' => 'Database connection error: ' . $e->getMessage()]);
exit;
}
$input = json_decode(file_get_contents('php://input'), true) ?? [];
$action = $_GET['action'] ?? ($_POST['action'] ?? ($input['action'] ?? ''));
switch ($action) {
case 'register':
$email = strtolower(trim($input['email'] ?? ''));
$password = trim($input['password'] ?? '');
if (!filter_var($email, FILTER_VALIDATE_EMAIL) || empty($password)) {
echo json_encode(['error' => true, 'message' => 'Valid email and password required']);
exit;
}
// 1. Explicitly check if email is already registered in MySQL
$checkStmt = $pdo->prepare("SELECT id FROM users WHERE LOWER(email) = ?");
$checkStmt->execute([$email]);
if ($checkStmt->fetch()) {
echo json_encode(['error' => true, 'message' => 'Email address is already registered']);
exit;
}
// 2. Assign admin role to first registered account
$countStmt = $pdo->query("SELECT COUNT(*) FROM users");
$isFirst = ($countStmt->fetchColumn() == 0);
$role = $isFirst ? 'admin' : 'user';
// 3. Insert new user into database
try {
$stmt = $pdo->prepare("INSERT INTO users (email, password, role) VALUES (?, ?, ?)");
$stmt->execute([$email, password_hash($password, PASSWORD_BCRYPT), $role]);
$userId = (int)$pdo->lastInsertId();
echo json_encode(['success' => true, 'user' => ['id' => $userId, 'email' => $email, 'role' => $role]]);
} catch (\PDOException $e) {
echo json_encode(['error' => true, 'message' => 'MySQL Error: ' . $e->getMessage()]);
}
break;
case 'login':
$email = strtolower(trim($input['email'] ?? ''));
$password = trim($input['password'] ?? '');
$stmt = $pdo->prepare("SELECT * FROM users WHERE LOWER(email) = ?");
$stmt->execute([$email]);
$user = $stmt->fetch();
if ($user && password_verify($password, $user['password'])) {
echo json_encode(['success' => true, 'user' => ['id' => (int)$user['id'], 'email' => $user['email'], 'role' => $user['role']]]);
} else {
echo json_encode(['error' => true, 'message' => 'Invalid email or password']);
}
break;
case 'shorten':
$url = trim($input['original_url'] ?? '');
$userId = $input['user_id'] ?? null;
$code = trim($input['short_code'] ?? '');
// Check DNS & domain validity
$host = parse_url($url, PHP_URL_HOST);
if (!$host || (!checkdnsrr($host, 'A') && !checkdnsrr($host, 'AAAA'))) {
echo json_encode(['error' => true, 'message' => 'Unable to verify destination URL']);
exit;
}
// Rate limit non-admin users (1 link/min)
$isAdmin = false;
if ($userId) {
$uStmt = $pdo->prepare("SELECT role FROM users WHERE id = ?");
$uStmt->execute([$userId]);
$uRes = $uStmt->fetch();
$isAdmin = ($uRes && $uRes['role'] === 'admin');
}
if (!$isAdmin) {
$limStmt = $pdo->prepare("SELECT COUNT(*) FROM links WHERE (user_id = ? OR user_id IS NULL) AND created_at > NOW() - INTERVAL 1 MINUTE");
$limStmt->execute([$userId]);
if ($limStmt->fetchColumn() >= 1) {
echo json_encode(['error' => true, 'message' => 'Rate limit reached (1 link/min). Please wait.']);
exit;
}
}
$stmt = $pdo->prepare("INSERT INTO links (short_code, original_url, user_id) VALUES (?, ?, ?)");
$stmt->execute([$code, $url, $userId]);
$linkId = $pdo->lastInsertId();
echo json_encode(['success' => true, 'link' => ['id' => $linkId, 'short_code' => $code, 'original_url' => $url, 'user_id' => $userId, 'clicks' => 0]]);
break;
case 'lookup':
$code = strtolower(trim($_GET['code'] ?? ($input['code'] ?? '')));
$reserved = ['links', 'admin', 'code', 'login', 'signup', 'home', 'index', 'api'];
if (in_array($code, $reserved)) {
echo json_encode(['error' => true, 'message' => 'Reserved route']);
exit;
}
$stmt = $pdo->prepare("SELECT * FROM links WHERE LOWER(short_code) = ?");
$stmt->execute([$code]);
$link = $stmt->fetch();
if ($link) {
$pdo->prepare("UPDATE links SET clicks = clicks + 1 WHERE id = ?")->execute([$link['id']]);
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
header("Location: " . $link['original_url'], true, 302);
exit;
}
echo json_encode(['success' => true, 'original_url' => $link['original_url']]);
} else {
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
header("Location: index.html?error=404&code=" . urlencode($code));
exit;
}
http_response_code(404);
echo json_encode(['error' => true, 'message' => 'Short code not found']);
}
break;
case 'get_links':
$userId = $input['user_id'] ?? null;
if (!$userId) {
echo json_encode(['success' => true, 'links' => []]);
exit;
}
$stmt = $pdo->prepare("SELECT * FROM links WHERE user_id = ? ORDER BY id DESC");
$stmt->execute([$userId]);
$links = $stmt->fetchAll();
echo json_encode(['success' => true, 'links' => $links]);
break;
case 'get_admin_data':
$userId = $input['user_id'] ?? null;
$uStmt = $pdo->prepare("SELECT role FROM users WHERE id = ?");
$uStmt->execute([$userId]);
$uRes = $uStmt->fetch();
if (!$uRes || $uRes['role'] !== 'admin') {
echo json_encode(['error' => true, 'message' => 'Unauthorized']);
exit;
}
$users = $pdo->query("SELECT id, email, role, created_at FROM users ORDER BY id ASC")->fetchAll();
$links = $pdo->query("SELECT * FROM links ORDER BY id DESC")->fetchAll();
echo json_encode(['success' => true, 'users' => $users, 'links' => $links]);
break;
case 'delete':
$linkId = $input['id'] ?? null;
$userId = $input['user_id'] ?? null;
if (!$linkId) {
echo json_encode(['error' => true, 'message' => 'Link ID required']);
exit;
}
$isAdmin = false;
if ($userId) {
$uStmt = $pdo->prepare("SELECT role FROM users WHERE id = ?");
$uStmt->execute([$userId]);
$uRes = $uStmt->fetch();
$isAdmin = ($uRes && $uRes['role'] === 'admin');
}
if ($isAdmin) {
$stmt = $pdo->prepare("DELETE FROM links WHERE id = ?");
$stmt->execute([$linkId]);
} else {
$stmt = $pdo->prepare("DELETE FROM links WHERE id = ? AND user_id = ?");
$stmt->execute([$linkId, $userId]);
}
echo json_encode(['success' => true]);
break;
case 'change_password':
$userId = $input['user_id'] ?? null;
$currPass = $input['current_password'] ?? '';
$newPass = $input['new_password'] ?? '';
if (!$userId || empty($currPass) || empty($newPass)) {
echo json_encode(['error' => true, 'message' => 'All fields required']);
exit;
}
$stmt = $pdo->prepare("SELECT * FROM users WHERE id = ?");
$stmt->execute([$userId]);
$user = $stmt->fetch();
if ($user && password_verify($currPass, $user['password'])) {
$uStmt = $pdo->prepare("UPDATE users SET password = ? WHERE id = ?");
$uStmt->execute([password_hash($newPass, PASSWORD_BCRYPT), $userId]);
echo json_encode(['success' => true]);
} else {
echo json_encode(['error' => true, 'message' => 'Incorrect current password']);
}
break;
default:
echo json_encode(['error' => true, 'message' => 'Invalid API Action']);
break;
}
?>