A modern URL shortening service built with a Node.js/Express backend and Next.js frontend. ZipLinker allows users to create short, manageable links from long URLs, track click statistics, and manage their links through a beautiful, responsive interface.
- User Authentication: Secure user registration and login with session-based authentication
- URL Shortening: Convert long URLs into short, unique links using nanoid
- Link Management: Create and list your shortened URLs
- Click Tracking: Automatic click count increment on link redirection
- Link Activation Control: Enable/disable short links as needed
- PostgreSQL Database: Robust data storage with Sequelize ORM
- RESTful API: Well-documented API endpoints for all functionality
- Input Validation: Zod-based schema validation
- Secure Passwords: bcrypt hashing for password storage
- Structured Logging: Winston logger for application monitoring
- CORS Support: Configurable cross-origin resource sharing
- Modern UI: Built with Next.js 16, React 19, and Tailwind CSS 4
- Beautiful Design: Striking landing page with WebGL-powered 3D animations
- Responsive Layout: Works seamlessly on mobile, tablet, and desktop
- Dark/Light Mode: Automatic theme detection with manual override
- Smooth Animations: Framer Motion for enhanced user experience
- Intuitive Navigation: Easy access to dashboard, link creation, and profile
- Real-time Updates: Instant feedback on link creation and management
- Error Handling: Graceful handling of API errors and validation issues
- Loading States: Visual feedback during asynchronous operations
ZipLinker follows a modern full-stack architecture:
βββββββββββββββββββ ββββββββββββββββββββββββ ββββββββββββββββββββββ
β Frontend β β Backend β β Database β
β (Next.js 16) βββββΊβ (Node.js/Express) βββββΊβ (PostgreSQL) β
β - React 19 β β - Sequelize ORM β β - Users Table β
β - Tailwind CSS 4β β - Express.js β β - Links Table β
β - Shadcn UI β β - PostgreSQL Driver β β β
β - Framer Motion β β - Winston Logger β β β
β - Three.js β β - Zod Validation β β β
βββββββββββββββββββ ββββββββββββββββββββββββ ββββββββββββββββββββββ
β² β
β βΌ
β ββββββββββββββββββββ
β β REST API β
β β (JSON) β
β ββββββββββββββββββββ
β
βββββββ HTTP/HTTPS βββββββββ
Before you begin, ensure you have installed:
- Node.js (v18 or higher)
- PostgreSQL (v12 or higher)
- pnpm (v8 or higher) - Package manager
- Git - Version control
git clone <repository-url>
cd UrlShortnercd backend
pnpm installcp sample.env .envEdit the .env file with your configuration:
# Server Configuration
PORT=3000
# Database Configuration
DB_NAME=your_database_name
DB_HOST=localhost
DB_PORT=5432
DB_USER=your_username
DB_PASSWORD=your_password
# Database Sync Options
FORCE_DROP_TABLE=false
FORCE_ALTER_TABLE=false
# Connection Pool
POOL_MIN=0
POOL_MAX=5
POOL_ACQUIRE=30000
POOL_IDLE=10000
# Retry Configuration
DB_CONN_MAX_RETRY=3
# Sequelize Model Options
TIMESTAMPS=true
UNDERSCORE=true
FREEZE_TABLE_NAME=true
# Timezone
TIMEZONE=UTC
# Session
SESSION_SECRET=your_secret_key_here
# CORS
ALLOWED_ORIGINS=http://localhost:3000,http://localhost:5173
# Application URLs
WEBSITE_URL=http://localhost:5173
BASE_URL=http://localhost:3000
LOG_DIR=./logs
# Environment
NODE_ENV=developmentCREATE DATABASE your_database_name;cd ../frontend
pnpm installCreate a .env.local file in the frontend directory:
# API Base URL
NEXT_PUBLIC_API_URL=http://localhost:3000
# Optional: Override default port
# NEXT_PUBLIC_APP_URL=http://localhost:5173Start both servers simultaneously:
# In one terminal - Backend
cd backend
pnpm dev
# In another terminal - Frontend
cd frontend
pnpm devThe frontend will be available at http://localhost:5173 The backend API will be available at http://localhost:3000
# Build backend
cd backend
pnpm build
pnpm start
# Build frontend (separate deployment)
cd frontend
pnpm build
pnpm startAll API endpoints are prefixed with /api/v1
POST /api/v1/users/register
Content-Type: application/json
{
"userName": "john_doe",
"email": "john@example.com",
"password": "password123"
}POST /api/v1/users/login
Content-Type: application/json
{
"email": "john@example.com",
"password": "password123"
}POST /api/v1/shortlinks/create
Content-Type: application/json
{
"longUrl": "https://www.example-long-url.com/very/long/path",
"isActive": true
}Response:
{
"statusCode": 201,
"message": "Short link created successfully",
"data": {
"urlId": "uuid",
"shortCode": "abc123",
"longUrl": "https://www.example-long-url.com/very/long/path",
"clicks": 0,
"isActive": true,
"createdAt": "2026-05-26T14:54:54.000Z",
"updatedAt": "2026-05-26T14:54:54.000Z"
}
}GET /api/v1/shortlinks/allResponse:
{
"statusCode": 200,
"message": "Links retrieved successfully",
"data": [
{
"urlId": "uuid",
"shortCode": "abc123",
"longUrl": "https://www.example-long-url.com/very/long/path",
"clicks": 42,
"isActive": true,
"createdAt": "2026-05-26T14:54:54.000Z",
"updatedAt": "2026-05-26T14:54:54.000Z"
}
]
}PATCH /api/v1/shortlinks/:urlId/toggleDELETE /api/v1/shortlinks/:urlIdGET /:shortCodeRedirects to the original long URL and increments click counter.
@sequelize/core- Sequelize ORM@sequelize/postgres- PostgreSQL dialect for Sequelizebcrypt- Password hashingcookie-parser- Cookie parsing middlewarecors- CORS middlewaredotenv- Environment variable loaderdottie- Object dot notation accessexpress- Web frameworkexpress-session- Session middlewarenanoid- Unique ID generatorpg- PostgreSQL clientpg-hstore- PostgreSQL hstore parseruuid- UUID generatorwinston- Logging libraryzod- Schema validation
next- React framework (v16.2.6)react- UI library (v19.2.4)react-dom- React DOM (v19.2.4)@base-ui/react- Base UI components@react-three/drei&@react-three/fiber- 3D renderingframer-motion- Animationslucide-react- Iconsnext-themes- Theme switchingshadcn- UI component librarytailwindcss- Utility-first CSS frameworktw-animate-css- Tailwind CSS animationsthree- 3D libraryclass-variance-authority- CSS variance utilityclsx- Class name utility
typescript- TypeScript compilertsx- TypeScript execution (backend)eslint- Code lintingeslint-config-next- Next.js ESLint config@types/*- TypeScript definitionstailwindcss&@tailwindcss/postcss- CSS processing
| Column | Type | Description |
|---|---|---|
| userId | UUID | Primary key |
| userName | String | User's display name |
| String | Unique email address | |
| password | String | Hashed password |
| isEmailVerified | Boolean | Email verification status |
| createdAt | DateTime | Account creation timestamp |
| updatedAt | DateTime | Last update timestamp |
| Column | Type | Description |
|---|---|---|
| urlId | UUID | Primary key |
| userId | UUID | Foreign key to Users |
| shortCode | String | Unique short code (nanoid) |
| longUrl | String | Original URL |
| clicks | Integer | Click counter |
| isActive | Boolean | Link activation status |
| createdAt | DateTime | Creation timestamp |
| updatedAt | DateTime | Last update timestamp |
| Variable | Description | Example |
|---|---|---|
PORT |
Server port number | 3000 |
DB_NAME |
PostgreSQL database name | ziplinker |
DB_HOST |
Database host | localhost |
DB_PORT |
Database port | 5432 |
DB_USER |
Database username | postgres |
DB_PASSWORD |
Database password | secret |
SESSION_SECRET |
Secret key for session signing | your_32_char_secret |
BASE_URL |
Base URL for generated short links | http://localhost:3000 |
WEBSITE_URL |
Frontend application URL | http://localhost:5173 |
ALLOWED_ORIGINS |
CORS allowed origins | http://localhost:3000,http://localhost:5173 |
LOG_DIR |
Directory for log files | ./logs |
NODE_ENV |
Environment (development/production) | development |
FORCE_DROP_TABLE |
Drop tables on sync (dev only) | false |
FORCE_ALTER_TABLE |
Alter tables on sync | false |
POOL_MIN |
Database connection pool minimum | 0 |
POOL_MAX |
Database connection pool maximum | 5 |
POOL_ACQUIRE |
Connection acquire timeout (ms) | 30000 |
POOL_IDLE |
Connection idle timeout (ms) | 10000 |
DB_CONN_MAX_RETRY |
Database connection max retries | 3 |
TIMESTAMPS |
Enable timestamps | true |
UNDERSCORE |
Use snake_case | true |
FREEZE_TABLE_NAME |
Prevent table name pluralization | true |
TIMEZONE |
Default timezone | UTC |
| Variable | Description | Example |
|---|---|---|
NEXT_PUBLIC_API_URL |
Backend API URL | http://localhost:3000 |
NEXT_PUBLIC_APP_URL |
Frontend URL (optional) | http://localhost:5173 |
Currently, the backend focuses on manual testing through API clients like Postman or curl. Future versions will include automated tests.
The frontend uses Next.js built-in testing capabilities. To run tests:
cd frontend
pnpm test # When tests are implementedZipLinker is designed to work across all device sizes:
- Mobile (< 640px): Optimized touch targets, vertical layout
- Tablet (640px - 1024px): Adaptive grid layouts
- Desktop (> 1024px): Full-featured layout with sidebar
- Password Hashing: All passwords are bcrypt hashed with salt
- Session Security: HTTP-only cookies with secure flags in production
- Input Validation: All API inputs validated with Zod schemas
- CORS Protection: Configured origins only
- SQL Injection Prevention: Sequelize ORM with parameterized queries
- Rate Limiting: Planned for future implementation
- XSS Protection: React auto-escaping + sanitization where needed
We welcome contributions to ZipLinker! Please follow these guidelines:
- Check if the issue already exists in Issues
- Create a new issue with clear title and description
- Include steps to reproduce, expected behavior, and actual behavior
- Add relevant screenshots or error logs
- Fork the repository
- Create a feature branch:
git checkout -b feature/amazing-feature - Make your changes following the code style
- Add tests for new functionality (when applicable)
- Commit your changes:
git commit -m 'feat: add amazing feature' - Push to the branch:
git push origin feature/amazing-feature - Open a Pull Request against the
mainbranch
- Follow TypeScript ESLint rules configured in both backend and frontend
- Use Prettier for code formatting (configured in respective projects)
- Write descriptive commit messages following Conventional Commits
- Keep PRs focused on a single feature or bug fix
-
Database Connection Failed
- Verify PostgreSQL is running
- Check
.envdatabase credentials - Ensure database exists:
CREATE DATABASE your_database_name;
-
Port Already in Use
- Change PORT in
.envor stop conflicting service - Use
lsof -i :3000to find conflicting process
- Change PORT in
-
Authentication Not Working
- Verify SESSION_SECRET is set (minimum 32 characters)
- Check cookie settings in browser
- Ensure CORS origins include frontend URL
-
API Connection Errors
- Verify backend is running on correct port
- Check
NEXT_PUBLIC_API_URLin.env.local - Ensure CORS settings in backend allow frontend origin
-
Build Failures
- Clear cache:
pnpm run clean(if available) or delete.nextfolder - Update dependencies:
pnpm update - Check for TypeScript errors
- Clear cache:
-
Performance Issues
- Check database indexes on frequently queried columns
- Monitor connection pool usage
- Enable query logging in development to spot slow queries
-
Session Expires Too Quickly
- Adjust express-session cookie.maxAge in backend config
- Consider using refresh tokens for long sessions
Q: Is ZipLinker suitable for production use? A: Yes, ZipLinker is production-ready with proper database, secure authentication, and scalable architecture. For high-traffic applications, consider adding a load balancer, Redis for session storage, and CDN for static assets.
Q: Can I use ZipLinker with a custom domain?
A: Yes, configure the BASE_URL environment variable to match your domain, and ensure your DNS points to your server.
Q: Does ZipLinker support link expiration?
A: Not currently in the core functionality, but the isActive field can be used to implement expiration logic in the frontend or via scheduled backend jobs.
Q: How are short codes generated? A: ZipLinker uses nanoid to generate cryptographically-secure, URL-friendly unique identifiers by default (size: 6 characters).
Q: Can I customize the short code length?
A: Yes, modify the nanoid generation in backend/src/utils/shortLink.util.ts to change the size parameter.
Q: Why use Express.js instead of a newer framework? A: Express provides maturity, stability, and extensive middleware ecosystem while remaining lightweight and performant for API services.
Q: Why Sequelize instead of Prisma or TypeORM? A: Sequelize offers excellent TypeScript support, mature feature set, and active maintenance. Evaluation showed it met our requirements best.
Q: Why Next.js for the frontend? A: Next.js provides excellent performance, SEO capabilities, intuitive file-based routing, and excellent developer experience with features like Hot Module Replacement.
Q: Is the 3D landing page essential? A: The 3D elements enhance user engagement and showcase modern web capabilities, but the core functionality works without JavaScript (though UX is degraded).
This project is licensed under the ISC License - see the LICENSE file for details.
- Node.js - JavaScript runtime
- Express.js - Web framework
- PostgreSQL - Advanced open-source database
- Sequelize - Promise-based Node.js ORM
- Next.js - React framework for production
- Tailwind CSS - Utility-first CSS framework
- nanoid - Tiny, secure ID generator
- Shadcn UI - Beautifully designed components
- Framer Motion - Animation library
- Lucide Icons - Beautiful open-source icons
- Winston - Logging library
- Zod - TypeScript-first schema validation