Source: Jolli-sample-repos/url-shortener Last Updated: 4/8/2026
Development Setup
Complete guide for setting up your development environment and contributing to the URL Shortener project.
Prerequisites
Required Software
-
Node.js (v16.x or higher)
- Download from https://nodejs.org/
- Verify:
node --version
-
npm (v7.x or higher)
- Comes with Node.js
- Verify:
npm --version
-
Git
- Download from https://git-scm.com/
- Verify:
git --version
-
Code Editor
- Recommended: Visual Studio Code
- Alternative: WebStorm, Sublime Text, vim, etc.
Recommended Tools
- Postman or Thunder Client - API testing
- Docker - Containerization (optional)
- nvm - Node version management (optional)
Getting Started
1. Clone the Repository
git clone <repository-url>
cd url-shortener2. Install Backend Dependencies
cd backend
npm installThis installs all dependencies from package.json:
- Runtime dependencies (Express, nanoid, Zod, etc.)
- Development dependencies (TypeScript, nodemon, etc.)
- Type definitions (@types/*)
3. Verify Installation
# Check TypeScript compilation
npm run build
# Start development server
npm run devYou should see:
URL Shortener API running on http://localhost:3001
API Documentation available at http://localhost:3001/api-docs4. Test the API
# In a new terminal
curl http://localhost:3001/healthExpected response:
{
"status": "healthy",
"timestamp": "2024-01-15T10:30:00.000Z"
}Project Structure
url-shortener/
├── backend/ # Backend API server
│ ├── src/ # TypeScript source code
│ │ ├── index.ts # Application entry point
│ │ ├── routes.ts # API route definitions
│ │ ├── models.ts # TypeScript interfaces
│ │ ├── storage.ts # Data storage layer
│ │ ├── generator.ts # Short code generation
│ │ ├── validator.ts # Request validation
│ │ └── swagger.ts # API documentation config
│ ├── dist/ # Compiled JavaScript (generated)
│ ├── node_modules/ # Dependencies (generated)
│ ├── package.json # Dependencies and scripts
│ └── tsconfig.json # TypeScript configuration
│
├── frontend/ # Web interface
│ ├── index.html # Main HTML page
│ ├── css/
│ │ └── styles.css # Styling
│ ├── js/
│ │ └── app.js # Application logic
│ └── assets/ # Images, fonts, etc.
│
└── aidan-doc/ # Documentation site (Docusaurus)
├── docs/ # Markdown documentation
├── src/ # React components
└── docusaurus.config.js # Site configurationDevelopment Workflow
Starting the Development Server
cd backend
npm run devThis uses nodemon to automatically restart the server when files change.
Features:
- Auto-reload on file save
- TypeScript compilation on-the-fly (via ts-node)
- Console logging for requests
- Full error stack traces
Making Changes
-
Edit a File
- Open
backend/src/routes.tsor any other file - Make your changes
- Save the file
- Open
-
Auto-Reload
- nodemon detects the change
- Server automatically restarts
- New code is loaded
-
Test Your Changes
- Use curl, Postman, or the frontend
- Check console output for logs
Building for Production
cd backend
npm run buildThis compiles TypeScript to JavaScript in the dist/ directory.
Output:
backend/dist/
├── index.js
├── routes.js
├── models.js
├── storage.js
├── generator.js
├── validator.js
└── swagger.jsRunning Production Build
npm startThis runs the compiled JavaScript from dist/.
Code Style and Conventions
TypeScript Conventions
Use Explicit Types:
// Good
function createUrl(longUrl: string): ShortUrl {
// ...
}
// Avoid
function createUrl(longUrl) {
// ...
}Use Interfaces for Data:
// Good
interface ShortUrl {
shortCode: string;
longUrl: string;
createdAt: string;
}
// Avoid
type ShortUrl = {
shortCode: string;
longUrl: string;
createdAt: string;
}Prefer Const:
// Good
const PORT = process.env.PORT || 3001;
// Avoid
let PORT = process.env.PORT || 3001;Naming Conventions
| Type | Convention | Example |
|---|---|---|
| Variables | camelCase | shortCode, longUrl |
| Constants | UPPER_SNAKE_CASE | DEFAULT_LENGTH, MAX_ATTEMPTS |
| Functions | camelCase | generateShortCode(), isExpired() |
| Classes | PascalCase | UrlStorage, ErrorHandler |
| Interfaces | PascalCase | ShortUrl, CreateUrlRequest |
| Files | kebab-case | url-storage.ts, short-code-generator.ts |
File Organization
Keep files focused:
- Each file should have a single responsibility
- Maximum ~300 lines per file
- Split large files into modules
Example Structure:
// routes.ts - Route definitions only
import { Router } from 'express';
import { createUrlHandler, getUrlHandler } from './handlers';
const router = Router();
router.post('/urls', createUrlHandler);
router.get('/urls/:shortCode', getUrlHandler);
export default router;// handlers.ts - Request handlers
export function createUrlHandler(req: Request, res: Response) {
// Handler logic
}
export function getUrlHandler(req: Request, res: Response) {
// Handler logic
}Error Handling
Always Handle Errors:
// Good
try {
const data = createUrlSchema.parse(req.body);
// Process data
} catch (error: any) {
if (error.errors) {
return res.status(400).json({ errors: error.errors });
}
return res.status(400).json({ error: error.message });
}Use Appropriate Status Codes:
- 200: Success (GET, PUT)
- 201: Created (POST)
- 204: No Content (DELETE)
- 400: Bad Request (validation errors)
- 404: Not Found
- 500: Server Error
Comments and Documentation
JSDoc for Public APIs:
/**
* Generate a unique short code for a URL
* @returns {string} A 6-character alphanumeric code
* @throws {Error} If unable to generate unique code after max attempts
*/
export function generateShortCode(): string {
// Implementation
}Inline Comments for Complex Logic:
// Check for collision and retry up to max attempts
let attempts = 0;
do {
code = nanoid();
attempts++;
if (attempts >= maxAttempts) {
throw new Error('Failed to generate unique code');
}
} while (urlStorage.exists(code));Testing
Manual Testing
Using curl:
# Create URL
curl -X POST http://localhost:3001/api/v1/urls \
-H "Content-Type: application/json" \
-d '{"longUrl": "https://example.com"}'
# Get URL
curl http://localhost:3001/api/v1/urls/AbC123
# Redirect
curl -L http://localhost:3001/r/AbC123Using Swagger UI:
- Open http://localhost:3001/api-docs
- Click “Try it out” on any endpoint
- Fill in parameters
- Click “Execute”
Unit Testing (Recommended)
While not currently implemented, here’s how to add testing:
Install Jest:
npm install --save-dev jest ts-jest @types/jest supertest @types/supertestConfigure Jest:
// jest.config.js
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
testMatch: ['**/__tests__/**/*.test.ts'],
collectCoverageFrom: ['src/**/*.ts'],
};Example Test:
// __tests__/generator.test.ts
import { generateShortCode, validateCustomCode } from '../src/generator';
describe('generateShortCode', () => {
it('should generate 6-character code', () => {
const code = generateShortCode();
expect(code).toHaveLength(6);
expect(code).toMatch(/^[a-zA-Z0-9]+$/);
});
});
describe('validateCustomCode', () => {
it('should reject short codes', () => {
const result = validateCustomCode('abc');
expect(result.valid).toBe(false);
expect(result.error).toContain('at least 4 characters');
});
it('should accept valid codes', () => {
const result = validateCustomCode('valid123');
expect(result.valid).toBe(true);
});
});Run Tests:
npm testDebugging
VS Code Debugging
Create .vscode/launch.json:
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Debug Backend",
"runtimeArgs": ["-r", "ts-node/register"],
"args": ["${workspaceFolder}/backend/src/index.ts"],
"cwd": "${workspaceFolder}/backend",
"env": {
"NODE_ENV": "development"
},
"sourceMaps": true,
"outFiles": ["${workspaceFolder}/backend/dist/**/*.js"]
}
]
}Usage:
- Set breakpoints in your code
- Press F5 or click “Run > Start Debugging”
- Use the debugging toolbar to step through code
Console Logging
The application includes request logging:
app.use((req, res, next) => {
console.log(`${new Date().toISOString()} - ${req.method} ${req.path}`);
next();
});Add Custom Logging:
console.log('Creating short URL:', { longUrl, customCode });
console.error('Error creating URL:', error);Chrome DevTools
For debugging the frontend:
- Open Chrome DevTools (F12)
- Go to the “Sources” tab
- Set breakpoints in
frontend/js/app.js - Reload the page
Common Development Tasks
Adding a New Endpoint
-
Define Route in
routes.ts:router.post('/urls/batch', batchCreateHandler); -
Implement Handler:
router.post('/urls/batch', (req: Request, res: Response) => { try { const urls = req.body.urls; // Array of URLs const results = urls.map(url => { // Create short URL for each }); res.status(201).json(results); } catch (error) { res.status(400).json({ error: error.message }); } }); -
Add Validation:
const batchCreateSchema = z.object({ urls: z.array(z.string().url()), }); -
Document with Swagger:
/** * @openapi * /api/v1/urls/batch: * post: * summary: Create multiple short URLs */
Modifying Storage
The storage layer is abstracted in storage.ts. To add a new method:
class UrlStorage {
// Existing methods...
findByDateRange(startDate: string, endDate: string): ShortUrl[] {
return Array.from(this.urls.values())
.filter(url => {
const created = new Date(url.createdAt);
return created >= new Date(startDate) && created <= new Date(endDate);
});
}
}Changing Validation Rules
Modify schemas in validator.ts:
export const createUrlSchema = z.object({
longUrl: z.string().url(),
customCode: z
.string()
.regex(/^[a-zA-Z0-9_-]+$/) // Allow hyphens and underscores
.min(3) // Reduce minimum length
.max(50) // Increase maximum length
.optional(),
expiresAt: z.string().datetime().optional(),
});Environment Setup
Development Environment Variables
Create .env in the backend directory:
# Server Configuration
PORT=3001
BASE_URL=http://localhost:3001
NODE_ENV=development
# Debug Settings
DEBUG=url-shortener:*
LOG_LEVEL=debugLoad with:
npm install dotenvimport 'dotenv/config';
const PORT = process.env.PORT || 3001;Git Workflow
Branch Naming
- Feature:
feature/add-batch-create - Bug fix:
fix/validation-error - Documentation:
docs/api-reference - Refactor:
refactor/storage-layer
Commit Messages
Follow conventional commits:
feat: add batch URL creation endpoint
fix: handle expired URLs in list endpoint
docs: update API documentation
refactor: extract validation logic
test: add unit tests for generatorMaking a Pull Request
-
Create Feature Branch:
git checkout -b feature/your-feature -
Make Changes and Commit:
git add . git commit -m "feat: add your feature" -
Push to Remote:
git push origin feature/your-feature -
Create Pull Request on GitHub/GitLab
IDE Setup
Visual Studio Code
Recommended Extensions:
- ESLint
- Prettier
- TypeScript Import Sorter
- Thunder Client (API testing)
- GitLens
Settings (.vscode/settings.json):
{
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"typescript.preferences.importModuleSpecifier": "relative",
"files.exclude": {
"**/node_modules": true,
"**/dist": true
}
}Troubleshooting
TypeScript Errors
# Clean and rebuild
rm -rf dist
npm run buildPort in Use
# Find and kill process on port 3001
# Linux/Mac:
lsof -ti:3001 | xargs kill -9
# Windows:
netstat -ano | findstr :3001
taskkill /PID <PID> /FModule Not Found
# Reinstall dependencies
rm -rf node_modules package-lock.json
npm installNext Steps
- API Reference - Understand the API
- Architecture Overview - Learn the system design
- Data Models - Understand data structures
- Contributing Guide - Contribution guidelines (if available)