Skip to content

Full-stack web application for automated website analysis. It crawls URLs to extract data such as HTML versions, heading structures, internal/external links, and broken links. Features real-time job progress, interactive dashboards, rich visualizations, and Docker-based deployment.

Notifications You must be signed in to change notification settings

damygoes/url-analyzer

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

88 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

URL Analyzer

A full-stack web application for crawling and analyzing websites. It detects broken links, extracts HTML metadata, visualizes page structure, and provides real-time crawling status.


πŸš€ Features

  • URL Analysis: Crawl and inspect any URL
  • Live Crawl Updates: See real-time crawling progress
  • Metrics Extraction:
    • HTML version
    • Heading structure (H1–H6)
    • Internal & external links
    • Broken links with status codes
    • Login form detection
  • Interactive Dashboard:
    • Filterable & sortable URL list
    • Bulk actions: re-analyze, delete
    • Responsive UI
  • Data Visualization: Recharts-powered graphs for links, headings, and more
  • Authentication: API key-based access
  • Dockerized Dev Environment

πŸ›  Tech Stack

Frontend

  • React 19 + TypeScript
  • Vite
  • Tailwind CSS + shadcn/ui
  • React Router v6
  • TanStack Query
  • Zustand
  • Recharts
  • React Hook Form + Zod
  • Axios

Backend

  • Go (Gin framework)
  • MySQL 8.0
  • Goroutines for concurrency
  • JWT-style API key validation

DevOps

  • Docker & Docker Compose
  • Volumes for persistent DB storage

πŸ“¦ Project Structure

.
β”œβ”€β”€ README.md
β”œβ”€β”€ docker-compose.yml
β”œβ”€β”€ url-analyzer-backend
β”‚Β Β  β”œβ”€β”€ Dockerfile
β”‚Β Β  β”œβ”€β”€ README.md
β”‚Β Β  β”œβ”€β”€ cmd
β”‚Β Β  β”‚Β Β  └── server
β”‚Β Β  β”‚Β Β      └── main.go
β”‚Β Β  β”œβ”€β”€ docs
β”‚Β Β  β”‚Β Β  β”œβ”€β”€ docs.go
β”‚Β Β  β”‚Β Β  β”œβ”€β”€ swagger.json
β”‚Β Β  β”‚Β Β  └── swagger.yaml
β”‚Β Β  β”œβ”€β”€ go.mod
β”‚Β Β  β”œβ”€β”€ go.sum
β”‚Β Β  β”œβ”€β”€ internal
β”‚Β Β  β”‚Β Β  β”œβ”€β”€ database
β”‚Β Β  β”‚Β Β  β”‚Β Β  β”œβ”€β”€ connection.go
β”‚Β Β  β”‚Β Β  β”‚Β Β  β”œβ”€β”€ init.go
β”‚Β Β  β”‚Β Β  β”‚Β Β  β”œβ”€β”€ interfaces.go
β”‚Β Β  β”‚Β Β  β”‚Β Β  β”œβ”€β”€ repository.go
β”‚Β Β  β”‚Β Β  β”‚Β Β  β”œβ”€β”€ repository_test.go
β”‚Β Β  β”‚Β Β  β”‚Β Β  └── utils.go
β”‚Β Β  β”‚Β Β  β”œβ”€β”€ handlers
β”‚Β Β  β”‚Β Β  β”‚Β Β  β”œβ”€β”€ system.go
β”‚Β Β  β”‚Β Β  β”‚Β Β  β”œβ”€β”€ urls.go
β”‚Β Β  β”‚Β Β  β”‚Β Β  └── urls_test.go
β”‚Β Β  β”‚Β Β  β”œβ”€β”€ middleware
β”‚Β Β  β”‚Β Β  β”‚Β Β  └── auth.go
β”‚Β Β  β”‚Β Β  β”œβ”€β”€ models
β”‚Β Β  β”‚Β Β  β”‚Β Β  └── models.go
β”‚Β Β  β”‚Β Β  └── services
β”‚Β Β  β”‚Β Β      β”œβ”€β”€ crawler_service.go
β”‚Β Β  β”‚Β Β      β”œβ”€β”€ crawler_service_test.go
β”‚Β Β  β”‚Β Β      └── interfaces.go
β”‚Β Β  β”œβ”€β”€ migrations
β”‚Β Β  β”‚Β Β  └── 001_initial_schema.sql
β”‚Β Β  └── pkg
β”‚Β Β      └── crawler
β”‚Β Β          β”œβ”€β”€ crawler.go
β”‚Β Β          β”œβ”€β”€ crawler_test.go
β”‚Β Β          └── debug_test.go
└── url-analyzer-frontend
    β”œβ”€β”€ Dockerfile.dev
    β”œβ”€β”€ components.json
    β”œβ”€β”€ eslint.config.js
    β”œβ”€β”€ global.d.ts
    β”œβ”€β”€ index.html
    β”œβ”€β”€ package.json
    β”œβ”€β”€ pnpm-lock.yaml
    β”œβ”€β”€ public
    β”œβ”€β”€ src
    β”‚Β Β  β”œβ”€β”€ App.tsx
    β”‚Β Β  β”œβ”€β”€ components
    β”‚Β Β  β”‚Β Β  β”œβ”€β”€ errors
    β”‚Β Β  β”‚Β Β  β”‚Β Β  β”œβ”€β”€ AppErrorBoundary.tsx
    β”‚Β Β  β”‚Β Β  β”‚Β Β  └── LoadingComponent.tsx
    β”‚Β Β  β”‚Β Β  β”œβ”€β”€ layout
    β”‚Β Β  β”‚Β Β  β”‚Β Β  β”œβ”€β”€ AppName.tsx
    β”‚Β Β  β”‚Β Β  β”‚Β Β  β”œβ”€β”€ Header.tsx
    β”‚Β Β  β”‚Β Β  β”‚Β Β  β”œβ”€β”€ RootLayout.tsx
    β”‚Β Β  β”‚Β Β  β”‚Β Β  └── sidebar
    β”‚Β Β  β”‚Β Β  β”‚Β Β      β”œβ”€β”€ Sidebar.tsx
    β”‚Β Β  β”‚Β Β  β”‚Β Β      β”œβ”€β”€ SidebarCollapseToggle.tsx
    β”‚Β Β  β”‚Β Β  β”‚Β Β      β”œβ”€β”€ SidebarMobileHeader.tsx
    β”‚Β Β  β”‚Β Β  β”‚Β Β      └── SidebarNavLinks.tsx
    β”‚Β Β  β”‚Β Β  β”œβ”€β”€ shared
    β”‚Β Β  β”‚Β Β  β”‚Β Β  └── ConfirmDialog.tsx
    β”‚Β Β  β”‚Β Β  └── ui
    β”‚Β Β  β”‚Β Β      β”œβ”€β”€ alert.tsx
    β”‚Β Β  β”‚Β Β      β”œβ”€β”€ badge.tsx
    β”‚Β Β  β”‚Β Β      β”œβ”€β”€ button.tsx
    β”‚Β Β  β”‚Β Β      β”œβ”€β”€ card.tsx
    β”‚Β Β  β”‚Β Β      β”œβ”€β”€ chart.tsx
    β”‚Β Β  β”‚Β Β      β”œβ”€β”€ checkbox.tsx
    β”‚Β Β  β”‚Β Β      β”œβ”€β”€ dialog.tsx
    β”‚Β Β  β”‚Β Β      β”œβ”€β”€ form.tsx
    β”‚Β Β  β”‚Β Β      β”œβ”€β”€ icon
    β”‚Β Β  β”‚Β Β      β”‚Β Β  β”œβ”€β”€ Icon.tsx
    β”‚Β Β  β”‚Β Β      β”‚Β Β  └── iconMapping.ts
    β”‚Β Β  β”‚Β Β      β”œβ”€β”€ input.tsx
    β”‚Β Β  β”‚Β Β      β”œβ”€β”€ label.tsx
    β”‚Β Β  β”‚Β Β      β”œβ”€β”€ pagination.tsx
    β”‚Β Β  β”‚Β Β      β”œβ”€β”€ progress.tsx
    β”‚Β Β  β”‚Β Β      β”œβ”€β”€ scroll-area.tsx
    β”‚Β Β  β”‚Β Β      β”œβ”€β”€ select.tsx
    β”‚Β Β  β”‚Β Β      β”œβ”€β”€ skeleton.tsx
    β”‚Β Β  β”‚Β Β      β”œβ”€β”€ sonner.tsx
    β”‚Β Β  β”‚Β Β      β”œβ”€β”€ table.tsx
    β”‚Β Β  β”‚Β Β      └── tooltip.tsx
    β”‚Β Β  β”œβ”€β”€ features
    β”‚Β Β  β”‚Β Β  β”œβ”€β”€ auth
    β”‚Β Β  β”‚Β Β  β”‚Β Β  β”œβ”€β”€ components
    β”‚Β Β  β”‚Β Β  β”‚Β Β  β”‚Β Β  β”œβ”€β”€ AuthForm.tsx
    β”‚Β Β  β”‚Β Β  β”‚Β Β  β”‚Β Β  β”œβ”€β”€ LogoutButton.tsx
    β”‚Β Β  β”‚Β Β  β”‚Β Β  β”‚Β Β  └── ProtectedRoute.tsx
    β”‚Β Β  β”‚Β Β  β”‚Β Β  β”œβ”€β”€ hooks
    β”‚Β Β  β”‚Β Β  β”‚Β Β  β”‚Β Β  └── useAuthSubmit.ts
    β”‚Β Β  β”‚Β Β  β”‚Β Β  β”œβ”€β”€ pages
    β”‚Β Β  β”‚Β Β  β”‚Β Β  β”‚Β Β  └── AuthPage.tsx
    β”‚Β Β  β”‚Β Β  β”‚Β Β  └── store
    β”‚Β Β  β”‚Β Β  β”‚Β Β      └── authStore.ts
    β”‚Β Β  β”‚Β Β  β”œβ”€β”€ dashboard
    β”‚Β Β  β”‚Β Β  β”‚Β Β  β”œβ”€β”€ __tests__
    β”‚Β Β  β”‚Β Β  β”‚Β Β  β”‚Β Β  └── DashboardPage.test.tsx
    β”‚Β Β  β”‚Β Β  β”‚Β Β  β”œβ”€β”€ components
    β”‚Β Β  β”‚Β Β  β”‚Β Β  β”‚Β Β  β”œβ”€β”€ BulkActionsCard.tsx
    β”‚Β Β  β”‚Β Β  β”‚Β Β  β”‚Β Β  β”œβ”€β”€ DashboardHeader.tsx
    β”‚Β Β  β”‚Β Β  β”‚Β Β  β”‚Β Β  └── URLTableSection.tsx
    β”‚Β Β  β”‚Β Β  β”‚Β Β  └── pages
    β”‚Β Β  β”‚Β Β  β”‚Β Β      └── DashboardPage.tsx
    β”‚Β Β  β”‚Β Β  β”œβ”€β”€ system
    β”‚Β Β  β”‚Β Β  β”‚Β Β  β”œβ”€β”€ components
    β”‚Β Β  β”‚Β Β  β”‚Β Β  β”‚Β Β  β”œβ”€β”€ ActiveJobsCard.tsx
    β”‚Β Β  β”‚Β Β  β”‚Β Β  β”‚Β Β  β”œβ”€β”€ CrawlerConfigCard.tsx
    β”‚Β Β  β”‚Β Β  β”‚Β Β  β”‚Β Β  β”œβ”€β”€ DatabaseErrorAlert.tsx
    β”‚Β Β  β”‚Β Β  β”‚Β Β  β”‚Β Β  β”œβ”€β”€ DatabaseStatsCard.tsx
    β”‚Β Β  β”‚Β Β  β”‚Β Β  β”‚Β Β  β”œβ”€β”€ HealthPageSkeleton.tsx
    β”‚Β Β  β”‚Β Β  β”‚Β Β  β”‚Β Β  └── SystemStatusCard.tsx
    β”‚Β Β  β”‚Β Β  β”‚Β Β  β”œβ”€β”€ hooks
    β”‚Β Β  β”‚Β Β  β”‚Β Β  β”‚Β Β  └── useSystem.ts
    β”‚Β Β  β”‚Β Β  β”‚Β Β  β”œβ”€β”€ pages
    β”‚Β Β  β”‚Β Β  β”‚Β Β  β”‚Β Β  └── HealthPage.tsx
    β”‚Β Β  β”‚Β Β  β”‚Β Β  └── utils
    β”‚Β Β  β”‚Β Β  β”‚Β Β      └── systemUtils.ts
    β”‚Β Β  β”‚Β Β  β”œβ”€β”€ url-analysis
    β”‚Β Β  β”‚Β Β  β”‚Β Β  β”œβ”€β”€ components
    β”‚Β Β  β”‚Β Β  β”‚Β Β  β”‚Β Β  β”œβ”€β”€ CrawlProgress.tsx
    β”‚Β Β  β”‚Β Β  β”‚Β Β  β”‚Β Β  β”œβ”€β”€ CrawlStatusLabel.tsx
    β”‚Β Β  β”‚Β Β  β”‚Β Β  β”‚Β Β  └── URLCrawlStatusCell.tsx
    β”‚Β Β  β”‚Β Β  β”‚Β Β  β”œβ”€β”€ store
    β”‚Β Β  β”‚Β Β  β”‚Β Β  β”‚Β Β  └── urlAnalysisStore.ts
    β”‚Β Β  β”‚Β Β  β”‚Β Β  └── utils
    β”‚Β Β  β”‚Β Β  β”‚Β Β      β”œβ”€β”€ crawlStatusConfig.ts
    β”‚Β Β  β”‚Β Β  β”‚Β Β      β”œβ”€β”€ isCrawlResultEmpty.ts
    β”‚Β Β  β”‚Β Β  β”‚Β Β      └── mapCrawlToURLStatus.ts
    β”‚Β Β  β”‚Β Β  β”œβ”€β”€ url-details
    β”‚Β Β  β”‚Β Β  β”‚Β Β  β”œβ”€β”€ components
    β”‚Β Β  β”‚Β Β  β”‚Β Β  β”‚Β Β  β”œβ”€β”€ ActionButtons.tsx
    β”‚Β Β  β”‚Β Β  β”‚Β Β  β”‚Β Β  β”œβ”€β”€ EmptyChartsSection.tsx
    β”‚Β Β  β”‚Β Β  β”‚Β Β  β”‚Β Β  β”œβ”€β”€ StatsGrid.tsx
    β”‚Β Β  β”‚Β Β  β”‚Β Β  β”‚Β Β  β”œβ”€β”€ URLDetailsPageSkeleton.tsx
    β”‚Β Β  β”‚Β Β  β”‚Β Β  β”‚Β Β  β”œβ”€β”€ URLInfoCard.tsx
    β”‚Β Β  β”‚Β Β  β”‚Β Β  β”‚Β Β  β”œβ”€β”€ broken-links
    β”‚Β Β  β”‚Β Β  β”‚Β Β  β”‚Β Β  β”‚Β Β  β”œβ”€β”€ BrokenLinksTable.tsx
    β”‚Β Β  β”‚Β Β  β”‚Β Β  β”‚Β Β  β”‚Β Β  β”œβ”€β”€ BrokenLinksTableHeader.tsx
    β”‚Β Β  β”‚Β Β  β”‚Β Β  β”‚Β Β  β”‚Β Β  β”œβ”€β”€ BrokenLinksTableRow.tsx
    β”‚Β Β  β”‚Β Β  β”‚Β Β  β”‚Β Β  β”‚Β Β  └── getStatusUtils.ts
    β”‚Β Β  β”‚Β Β  β”‚Β Β  β”‚Β Β  └── charts
    β”‚Β Β  β”‚Β Β  β”‚Β Β  β”‚Β Β      β”œβ”€β”€ HeadingDistributionChart.tsx
    β”‚Β Β  β”‚Β Β  β”‚Β Β  β”‚Β Β      └── LinkDistributionChart.tsx
    β”‚Β Β  β”‚Β Β  β”‚Β Β  β”œβ”€β”€ pages
    β”‚Β Β  β”‚Β Β  β”‚Β Β  β”‚Β Β  └── URLDetailsPage.tsx
    β”‚Β Β  β”‚Β Β  β”‚Β Β  β”œβ”€β”€ sections
    β”‚Β Β  β”‚Β Β  β”‚Β Β  β”‚Β Β  β”œβ”€β”€ BrokenLinksSection.tsx
    β”‚Β Β  β”‚Β Β  β”‚Β Β  β”‚Β Β  └── ChartsSection.tsx
    β”‚Β Β  β”‚Β Β  β”‚Β Β  └── utils
    β”‚Β Β  β”‚Β Β  β”‚Β Β      └── urlDetailsUtils.ts
    β”‚Β Β  β”‚Β Β  └── urls
    β”‚Β Β  β”‚Β Β      β”œβ”€β”€ components
    β”‚Β Β  β”‚Β Β      β”‚Β Β  β”œβ”€β”€ AddURLDialog.tsx
    β”‚Β Β  β”‚Β Β      β”‚Β Β  β”œβ”€β”€ URLFilters.tsx
    β”‚Β Β  β”‚Β Β      β”‚Β Β  β”œβ”€β”€ url-badge
    β”‚Β Β  β”‚Β Β      β”‚Β Β  β”‚Β Β  β”œβ”€β”€ StatusBadge.tsx
    β”‚Β Β  β”‚Β Β      β”‚Β Β  β”‚Β Β  └── URLStatusBadge.tsx
    β”‚Β Β  β”‚Β Β      β”‚Β Β  └── url-table
    β”‚Β Β  β”‚Β Β      β”‚Β Β      β”œβ”€β”€ PaginationControls.tsx
    β”‚Β Β  β”‚Β Β      β”‚Β Β      β”œβ”€β”€ SortableHeader.tsx
    β”‚Β Β  β”‚Β Β      β”‚Β Β      β”œβ”€β”€ URLTable.tsx
    β”‚Β Β  β”‚Β Β      β”‚Β Β      β”œβ”€β”€ URLTableEmptyState.tsx
    β”‚Β Β  β”‚Β Β      β”‚Β Β      β”œβ”€β”€ URLTableHeader.tsx
    β”‚Β Β  β”‚Β Β      β”‚Β Β      β”œβ”€β”€ URLTableRow.tsx
    β”‚Β Β  β”‚Β Β      β”‚Β Β      └── URLTableSkeleton.tsx
    β”‚Β Β  β”‚Β Β      β”œβ”€β”€ hooks
    β”‚Β Β  β”‚Β Β      β”‚Β Β  └── useURLs.ts
    β”‚Β Β  β”‚Β Β      β”œβ”€β”€ store
    β”‚Β Β  β”‚Β Β      β”‚Β Β  └── urlStore.ts
    β”‚Β Β  β”‚Β Β      β”œβ”€β”€ types.ts
    β”‚Β Β  β”‚Β Β      └── utils
    β”‚Β Β  β”‚Β Β          └── urlStatusConfig.ts
    β”‚Β Β  β”œβ”€β”€ hooks
    β”‚Β Β  β”‚Β Β  └── useDebounce.ts
    β”‚Β Β  β”œβ”€β”€ index.css
    β”‚Β Β  β”œβ”€β”€ lib
    β”‚Β Β  β”‚Β Β  └── utils.ts
    β”‚Β Β  β”œβ”€β”€ main.tsx
    β”‚Β Β  β”œβ”€β”€ mocks
    β”‚Β Β  β”‚Β Β  β”œβ”€β”€ handlers.ts
    β”‚Β Β  β”‚Β Β  └── node.ts
    β”‚Β Β  β”œβ”€β”€ router
    β”‚Β Β  β”‚Β Β  β”œβ”€β”€ Router.tsx
    β”‚Β Β  β”‚Β Β  └── routerConfig.tsx
    β”‚Β Β  β”œβ”€β”€ shared
    β”‚Β Β  β”‚Β Β  β”œβ”€β”€ api
    β”‚Β Β  β”‚Β Β  β”‚Β Β  └── client.ts
    β”‚Β Β  β”‚Β Β  β”œβ”€β”€ types
    β”‚Β Β  β”‚Β Β  β”‚Β Β  └── api.ts
    β”‚Β Β  β”‚Β Β  └── utils
    β”‚Β Β  β”‚Β Β      β”œβ”€β”€ getErrorMessage.ts
    β”‚Β Β  β”‚Β Β      └── pluralize.ts
    β”‚Β Β  β”œβ”€β”€ test
    β”‚Β Β  β”‚Β Β  └── test-utils.tsx
    β”‚Β Β  └── vite-env.d.ts
    β”œβ”€β”€ tsconfig.app.json
    β”œβ”€β”€ tsconfig.json
    β”œβ”€β”€ tsconfig.node.json
    β”œβ”€β”€ vite.config.ts
    β”œβ”€β”€ vitest.config.ts
    └── vitest.setup.ts

🐳 Run with Docker (Recommended)

  1. Clone the repository
git clone https://github.com/YOUR_USERNAME/url-analyzer.git
cd url-analyzer
  1. Start all services
docker-compose up --build
  1. Access the app

πŸ’» Local Development (without Docker)

  1. Backend (Go)
cd url-analyzer-backend
go mod download

# Create DB manually or use Docker MySQL
mysql -u root -p -e "CREATE DATABASE url_analyzer;"

# Load initial tables
mysql -u root -p url_analyzer < migrations/001_create_tables.sql

cp .env.example .env
go run main.go
  1. Frontend (Vite + React)
cd url-analyzer-frontend
pnpm install

cp .env.example .env
pnpm run dev

πŸ§ͺ Running Tests

  1. Frontend
cd url-analyzer-frontend
pnpm run test           # Run tests
  1. Backend
cd url-analyzer-backend
go test ./...
go test -cover ./...

πŸ” Environment Variables

  1. Backend
| Variable      | Description                 |
|---------------|-----------------------------|
| `DB_HOST`     | MySQL host (`mysql`)        |
| `DB_PORT`     | MySQL port (`3306`)         |
| `DB_USER`     | MySQL user (`root`)         |
| `DB_PASSWORD` | MySQL password              |
| `DB_NAME`     | Database name               |
| `API_KEY`     | API key for requests        |
| `PORT`        | Backend server port         |
  1. Frontend
| Variable        | Description              |
|-----------------|--------------------------|
| `VITE_API_URL`  | Backend API base URL     |

πŸ”§ API Overview

All requests must include:

Authorization: test-api-key-12345

API Endpoints

URLs

  • GET /api/urls – List URLs
  • POST /api/urls – Create a new crawl job
  • GET /api/urls/:id – Get crawl details
  • PUT /api/urls/:id/start – Start crawling
  • PUT /api/urls/:id/stop – Stop crawl
  • PUT /api/urls/:id/restart – Restart crawl
  • GET /api/urls/:id/status – Poll job status
  • DELETE /api/urls – Bulk delete

System

  • GET /api/health – Health check
  • GET /api/stats – System usage stats

πŸ“ License

This project is licensed under the MIT License.

πŸ‘€ Author

Damilola Bada
Github | LinkedIn

About

Full-stack web application for automated website analysis. It crawls URLs to extract data such as HTML versions, heading structures, internal/external links, and broken links. Features real-time job progress, interactive dashboards, rich visualizations, and Docker-based deployment.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published