A Dockerized full-stack web application that displays the current host's public and internal IP addresses, resolves domain names to IP addresses, and maintains a visible history of resolved domains.
βββββββββββββββ HTTP/REST API βββββββββββββββ
β Frontend β ββββββββββββββββββββββββββββ> β Backend β
β (React + β β (Express + β
β TypeScript) β <ββββββββββββββββββββββββββββ β Node.js) β
β β β β
β Port: 3000 β β Port: 4000 β
βββββββββββββββ βββββββββββββββ
β β
β β
ββββββββββββββββββββββββββββββββββββββββββββββββ
Docker Network
- Frontend β Backend: The frontend makes REST API calls to the backend
- API Proxy: In Docker, nginx (frontend container) proxies
/api/*requests to the backend service - Backend Services:
- Fetches public IP from external service (ipify.org)
- Resolves internal IP from system network interfaces
- Performs DNS resolution using Node.js
dnsmodule
- Frontend: React 18 + TypeScript + Vite + SCSS
- Backend: Node.js 20 + Express + TypeScript
- Containerization: Docker + Docker Compose
- Web Server: Nginx (for serving frontend and proxying API requests)
- Package Manager: npm
Returns the current host's public and internal IP addresses.
Response:
{
"publicIp": "x.x.x.x",
"internalIp": "x.x.x.x"
}Implementation Notes:
- Public IP is fetched from
https://api.ipify.org - Internal IP is resolved from system network interfaces (first non-loopback IPv4)
Resolves a domain name to an IP address.
Request Body:
{
"domain": "example.com"
}Response:
{
"domain": "example.com",
"ip": "x.x.x.x"
}Error Responses:
400 Bad Request: Invalid or missing domain500 Internal Server Error: DNS resolution failed
Implementation Notes:
- Validates domain format (basic regex check)
- Uses Node.js
dns.lookup()for resolution - Returns IPv4 addresses only
Health check endpoint for Docker health checks.
Response:
{
"status": "ok"
}- Docker and Docker Compose installed
- Node.js 20+ (for local development)
- Clone the repository:
git clone <repository-url>
cd ip-resolver- Build and start all services:
docker compose up --build- Access the application:
- Frontend: http://localhost:3000
- Backend API: http://localhost:4000
- Stop the services:
docker compose down- Navigate to backend directory:
cd backend- Install dependencies:
npm install- Run in development mode:
npm run devThe backend will run on http://localhost:4000
- Navigate to frontend directory:
cd frontend- Install dependencies:
npm install- Run in development mode:
npm run devThe frontend will run on http://localhost:3000 (Vite dev server proxies /api to backend)
cd backend
# Type check
npm run typecheck
# Build
npm run build
# Run production build
npm startcd frontend
# Type check
npm run typecheck
# Build for production
npm run build
# Preview production build
npm run previewThe CI pipeline (.github/workflows/ci.yml) runs on every push and pull request:
-
Backend Job:
- Installs dependencies
- Runs TypeScript type checking
- (Optional) Runs tests if present
-
Frontend Job:
- Installs dependencies
- Runs TypeScript type checking
- Builds production assets
-
Docker Build Job:
- Builds backend Docker image
- Builds frontend Docker image
- Validates
docker-compose.ymlsyntax
Why these checks?
- Type checking catches errors before runtime
- Building validates that code compiles correctly
- Docker builds ensure containers can be created successfully
Equivalent pipeline (.gitlab-ci.yml) with stages:
install: Install dependenciestest: Type checkingbuild: Build frontend assetsdocker-build: Build Docker images
-
State Management: Using React hooks (
useState,useEffect) - simple and sufficient for this application. No need for Redux or Context API. -
History Duplicates: Duplicate domains are allowed in history. Each resolution creates a new entry with a timestamp. This allows tracking if a domain's IP changes over time.
-
Loading States: Separate loading states for IP fetch and domain resolution provide better UX feedback.
-
Error Handling: User-friendly error messages displayed inline with clear styling, via a reusable
ErrorMessagecomponent. -
Styling: Custom SCSS (no heavy UI frameworks) with shared variables, component-scoped styles, and a responsive, mobile-friendly layout.
-
Componentization: UI split into focused components (
Header,IpAddresses,DomainResolver,HistoryList,HistoryItem,Spinner,ErrorMessage) to keep concerns separated and theAppcomponent thin.
-
Public IP Service: Using
ipify.org- simple, reliable, no authentication required. Alternative services could be used (ip-api.com, icanhazip.com). -
Internal IP Resolution: Uses Node.js
os.networkInterfaces()to find the first non-loopback IPv4 address. This typically returns the primary network interface's IP. -
DNS Resolution: Using Node.js
dns.lookup()which respects system DNS configuration and/etc/hostsfile. -
Error Handling: Clear error messages with appropriate HTTP status codes (400 for client errors, 500 for server errors).
-
CORS: Enabled for all origins (suitable for development). In production, restrict to specific domains.
-
Multi-stage Builds: Both frontend and backend use multi-stage Dockerfiles to minimize image size.
-
Nginx Proxy: Frontend nginx configuration proxies
/apirequests to backend service. This allows the frontend to make relative API calls (no hard-coded API base URL in the built assets). -
Health Checks: Backend includes a health check endpoint for Docker health monitoring.
-
Network Isolation: Both services run on the same Docker network for secure inter-container communication.
-
Port Mapping:
- Frontend: 3000 (host) β 80 (container)
- Backend: 4000 (host) β 4000 (container)
-
Vite Environment Variables: Vite bundles environment variables at build time, not runtime. This means the API URL is baked into the build. In production, we use nginx to proxy requests, so relative URLs work. Alternative: Use runtime configuration or a separate config endpoint.
-
No Database: History is stored in frontend state only. On page refresh, history is lost. For persistence, you'd need a database (PostgreSQL, MongoDB, etc.) and additional API endpoints.
-
No Authentication: The API is open. In production, you'd want authentication/authorization.
-
Simple Domain Validation: Basic regex validation. For production, use a more robust domain validation library.
ip-resolver/
βββ backend/
β βββ src/
β β βββ server.ts # Express server and API endpoints
β βββ Dockerfile # Backend Docker image
β βββ package.json
β βββ tsconfig.json
βββ frontend/
β βββ src/
β β βββ App.tsx # Main React component (composition only)
β β βββ App.scss # App-level styles
β β βββ main.tsx # React entry point
β β βββ styles/
β β β βββ index.scss # Global styles
β β β βββ variables.scss # Shared SCSS variables (colors, etc.)
β β βββ components/
β β β βββ Header/ # Header component + styles
β β β βββ IpAddresses/ # IP addresses section + styles
β β β βββ DomainResolver/ # Resolve-domain form + styles
β β β βββ HistoryList/ # History list container + styles
β β β βββ HistoryItem/ # Single history item + styles
β β β βββ Spinner/ # Reusable loading spinner + styles
β β β βββ ErrorMessage/ # Reusable error message + styles
β β βββ services/
β β β βββ api.ts # Frontend API client (fetch/resolve)
β β βββ types/
β β β βββ index.ts # Shared TypeScript interfaces
β β βββ vite-env.d.ts # Vite TypeScript helpers
β βββ public/
β β βββ index.html
β β βββ vite.svg
β βββ Dockerfile # Frontend Docker image (Vite build + nginx)
β βββ nginx.conf # Nginx configuration (serves app + /api proxy)
β βββ vite.config.ts
β βββ package.json
β βββ tsconfig.json
βββ .github/
β βββ workflows/
β βββ ci.yml # GitHub Actions CI
βββ .gitlab-ci.yml # GitLab CI
βββ docker-compose.yml # Docker Compose configuration
βββ .gitignore
βββ README.md
PORT: Server port (default: 4000)NODE_ENV: Environment (development/production)
VITE_API_URL: API base URL (default: empty, uses relative paths)
- Add database persistence for resolution history
- Implement authentication/authorization
- Add rate limiting for API endpoints
- Support IPv6 resolution
- Add bulk domain resolution
- Export history as CSV/JSON
- Add unit and integration tests
- Implement caching for frequently resolved domains
ISC