WorldWise
Wednesday, January 01, 2025
Inside WorldWise: Key Parts of the Project
The WorldWise project is a full-stack application integrating a modern React frontend with a Node‑express backend. Here’s a look at some of its key components.
Modern Client-Side Architecture
1. Component-Driven UI
The client side is built with React and leverages TypeScript for type safety. The folder is organized by feature, allowing for reusable components. For example, the City.tsx and CityItem.tsx files display single city details and list items. The component-driven approach promotes code reuse and ease of maintenance.
Sample Code: City Component
import styles from './City.module.css'; import { formatDate } from '../helpers/formatDate'; import ButtonBack from './Buttons/ButtonBack'; import { useParams } from 'react-router-dom'; import { useCities } from '../context/useCities'; import { useEffect } from 'react'; import Spinner from './Spinner'; function City() { const { id } = useParams(); const { getCityById, currentCity, isLoading } = useCities(); useEffect(() => { getCityById(id as string); }, [id, getCityById]); if (!currentCity) return null; const { cityName, emoji, date, notes } = currentCity; if (isLoading) return <Spinner />; return ( <div className={styles.city}> {/* ... UI for showing city details ... */} <ButtonBack /> </div> ); } export default City;
2. Context for State Management
State is managed via two primary contexts:
- AuthContext: Manages user authentication. It provides login/logout functionality and protects routes using a ProtectedRoute component.
- CitiesContext: Handles the list of cities, current selection, and CRUD operations using a reducer pattern and localStorage for persistence.
Sample Code: AuthProvider
import { createContext, useReducer, ReactNode } from 'react'; const FAKE_USER = { name: 'Yzel', email: 'yzel@example.com', password: 'qwerty', avatar: 'https://i.pravatar.cc/100?u=zz' }; const initialState = { user: null, isAuthenticated: false }; function authReducer(state, action) { switch (action.type) { case 'LOGIN': return { ...state, user: action.payload, isAuthenticated: true }; case 'LOGOUT': return { ...state, user: null, isAuthenticated: false }; default: throw new Error('Unhandled action type'); } } export const AuthContext = createContext(undefined); function AuthProvider({ children }) { const [{ user, isAuthenticated }, dispatch] = useReducer(authReducer, initialState); function login(email, password) { if (email === FAKE_USER.email && password === FAKE_USER.password) { dispatch({ type: 'LOGIN', payload: FAKE_USER }); } } function logout() { dispatch({ type: 'LOGOUT' }); } return ( <AuthContext.Provider value={{ user, isAuthenticated, login, logout }}> {children} </AuthContext.Provider> ); } export { AuthProvider };
3. Interactive Map Integration
The application uses React Leaflet to display an interactive map. Users can click on the map to add a city, making the experience visual and engaging.
Sample Code: Map Component
import { MapContainer, Marker, Popup, TileLayer, useMap, useMapEvents } from 'react-leaflet'; import { useEffect, useState } from 'react'; import { useCities } from '../context/useCities'; import { useGeolocation } from '../hooks/useGeolocation'; import Button from './Buttons/Button'; import { useURLPosition } from '../hooks/useURLPosition'; export default function Map() { const { cities } = useCities(); const [mapPosition, setMapPosition] = useState({ lat: 51.505, lng: -0.09 }); const { isLoading: isLoadingPosition, position: geoLocationPosition, getPosition } = useGeolocation(); const { latitude: lat, longitude: lng } = useURLPosition(); useEffect(() => { if (!isNaN(lat) && !isNaN(lng)) { setMapPosition({ lat, lng }); } }, [lat, lng]); useEffect(() => { if (geoLocationPosition) { setMapPosition(geoLocationPosition); } }, [geoLocationPosition]); return ( <div className="mapContainer"> {!geoLocationPosition && ( <Button style="position" onClick={getPosition}> {isLoadingPosition ? 'Loading...' : 'Use my position'} </Button> )} <MapContainer center={[mapPosition.lat, mapPosition.lng]} zoom={13}> <TileLayer url="https://{s}.tile.openstreetmap.fr/hot/{z}/{x}/{y}.png" /> {cities.map(city => ( <Marker position={city.position} key={city.id}> <Popup> <span>{city.emoji}</span> <span>{city.cityName}</span> </Popup> </Marker> ))} <ChangeCenter position={mapPosition} /> <DetectClick /> </MapContainer> </div> ); }
Robust Backend with Express and MySQL
The backend is powered by Node‑Express and connects to a MySQL database. Key endpoints include fetching all cities, retrieving a single city by ID, and adding new city records.
1. Express Server Setup
The server is configured with CORS, JSON parsing, and environment variables using dotenv. The MySQL connection is managed via a connection pool, ensuring efficient handling of database queries.
Sample Code: Express Server Initialization
import express from 'express'; import mysql from 'mysql2'; import dotenv from 'dotenv'; import cors from 'cors'; dotenv.config(); const app = express(); app.use(express.json()); app.use(cors()); const pool = mysql.createPool({ host: process.env.MYSQL_HOST, user: process.env.MYSQL_USER, password: process.env.MYSQL_PASSWORD, database: process.env.MYSQL_DATABASE, waitForConnections: true, connectionLimit: 10, queueLimit: 0, }); app.get('/api/cities', (req, res) => { pool.query('SELECT * FROM cities', (err, results) => { if (err) return res.status(500).json({ error: err.message }); res.json(results); }); }); // Additional endpoints for GET by ID and POST for creating cities... const PORT = process.env.PORT || 5000; app.listen(PORT, () => { console.log(`Server is running on port ${PORT}`); });
2. Data Insertion Script
A helper Python script (insertTable.py) is included to insert sample data into the MySQL database, to make sure that the database is filled with initial city data.
© 2025 Amadou Seck. Published on aseck.io