Skip to Content
Development Setup

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

  1. Node.js (v16.x or higher)

  2. npm (v7.x or higher)

    • Comes with Node.js
    • Verify: npm --version
  3. Git

  4. Code Editor

    • Recommended: Visual Studio Code
    • Alternative: WebStorm, Sublime Text, vim, etc.
  • 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-shortener

2. Install Backend Dependencies

cd backend npm install

This 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 dev

You should see:

URL Shortener API running on http://localhost:3001 API Documentation available at http://localhost:3001/api-docs

4. Test the API

# In a new terminal curl http://localhost:3001/health

Expected 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 configuration

Development Workflow

Starting the Development Server

cd backend npm run dev

This 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

  1. Edit a File

    • Open backend/src/routes.ts or any other file
    • Make your changes
    • Save the file
  2. Auto-Reload

    • nodemon detects the change
    • Server automatically restarts
    • New code is loaded
  3. Test Your Changes

    • Use curl, Postman, or the frontend
    • Check console output for logs

Building for Production

cd backend npm run build

This compiles TypeScript to JavaScript in the dist/ directory.

Output:

backend/dist/ ├── index.js ├── routes.js ├── models.js ├── storage.js ├── generator.js ├── validator.js └── swagger.js

Running Production Build

npm start

This 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

TypeConventionExample
VariablescamelCaseshortCode, longUrl
ConstantsUPPER_SNAKE_CASEDEFAULT_LENGTH, MAX_ATTEMPTS
FunctionscamelCasegenerateShortCode(), isExpired()
ClassesPascalCaseUrlStorage, ErrorHandler
InterfacesPascalCaseShortUrl, CreateUrlRequest
Fileskebab-caseurl-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/AbC123

Using Swagger UI:

  1. Open http://localhost:3001/api-docs 
  2. Click “Try it out” on any endpoint
  3. Fill in parameters
  4. Click “Execute”

While not currently implemented, here’s how to add testing:

Install Jest:

npm install --save-dev jest ts-jest @types/jest supertest @types/supertest

Configure 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 test

Debugging

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:

  1. Set breakpoints in your code
  2. Press F5 or click “Run > Start Debugging”
  3. 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:

  1. Open Chrome DevTools (F12)
  2. Go to the “Sources” tab
  3. Set breakpoints in frontend/js/app.js
  4. Reload the page

Common Development Tasks

Adding a New Endpoint

  1. Define Route in routes.ts:

    router.post('/urls/batch', batchCreateHandler);
  2. 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 }); } });
  3. Add Validation:

    const batchCreateSchema = z.object({ urls: z.array(z.string().url()), });
  4. 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=debug

Load with:

npm install dotenv
import '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 generator

Making a Pull Request

  1. Create Feature Branch:

    git checkout -b feature/your-feature
  2. Make Changes and Commit:

    git add . git commit -m "feat: add your feature"
  3. Push to Remote:

    git push origin feature/your-feature
  4. 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 build

Port 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> /F

Module Not Found

# Reinstall dependencies rm -rf node_modules package-lock.json npm install

Next Steps