Final Project: Address Service API
Build a comprehensive Address Service that combines all the concepts from this course into a unified API.
Project Overview
You'll create an AddressService class that provides:
- Address parsing for multiple countries
- Postal code validation with GeoNames
- Address normalization (USPS standards)
- Geocoding with Nominatim
- Address deduplication
Architecture
1┌─────────────────────────────────────────────────────────┐2│ AddressService │3├─────────────────────────────────────────────────────────┤4│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────┐ │5│ │ Parser │ │ Validator │ │ Normalizer │ │6│ └─────────────┘ └─────────────┘ └─────────────────┘ │7│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────┐ │8│ │ Geocoder │ │ Deduplicator│ │ Formatter │ │9│ └─────────────┘ └─────────────┘ └─────────────────┘ │10└─────────────────────────────────────────────────────────┘
Core Components
1. Address Parser
javascript1class AddressParser {2 parse(rawAddress, country = 'US') {3 // Extract components from raw address string4 return {5 street: '123 Main St',6 unit: 'Apt 4B',7 city: 'New York',8 state: 'NY',9 postalCode: '10001',10 country: 'US'11 };12 }13}
2. Postal Code Validator
javascript1class PostalCodeValidator {2 validate(postalCode, countryCode) {3 // Format validation using country-specific regex patterns4 if (!this.validateFormat(postalCode, countryCode)) {5 return { valid: false, reason: 'Invalid format' };6 }78 return { valid: true, postalCode, countryCode };9 }10}
3. Address Normalizer
javascript1class AddressNormalizer {2 normalize(address) {3 return {4 ...address,5 street: this.normalizeStreet(address.street),6 state: this.normalizeState(address.state),7 postalCode: this.normalizePostalCode(address.postalCode)8 };9 }10}
4. Geocoder
javascript1class Geocoder {2 async geocode(address) {3 // Convert address to coordinates4 return { lat: 40.7484, lon: -73.9967 };5 }67 async reverseGeocode(lat, lon) {8 // Convert coordinates to address9 return { displayName: '123 Main St, NYC' };10 }11}
5. Address Deduplicator
javascript1class AddressDeduplicator {2 findDuplicates(addresses, threshold = 0.85) {3 // Find duplicate pairs above similarity threshold4 // Returns array of { address1, address2, similarity }5 return [6 { address1: addresses[0], address2: addresses[2], similarity: 0.92 }7 ];8 }9}
Unified Service
javascript1class AddressService {2 constructor(options = {}) {3 this.parser = new AddressParser();4 this.validator = new PostalCodeValidator(options.geonamesUsername);5 this.normalizer = new AddressNormalizer();6 this.geocoder = new Geocoder();7 this.deduplicator = new AddressDeduplicator();8 }910 async processAddress(rawAddress, country = 'US') {11 // 1. Parse12 const parsed = this.parser.parse(rawAddress, country);1314 // 2. Normalize15 const normalized = this.normalizer.normalize(parsed);1617 // 3. Validate postal code18 const validation = this.validator.validate(19 normalized.postalCode,20 country21 );2223 // 4. Geocode24 const coordinates = await this.geocoder.geocode(normalized);2526 return {27 original: rawAddress,28 parsed,29 normalized,30 validation,31 coordinates,32 confidence: this.calculateConfidence(validation, coordinates)33 };34 }3536 async processBatch(addresses, country = 'US') {37 // Process multiple addresses38 const results = [];39 for (const address of addresses) {40 results.push(await this.processAddress(address, country));41 }4243 // Find duplicates44 const duplicates = this.deduplicator.findDuplicates(45 results.map(r => r.normalized)46 );4748 return { results, duplicates };49 }50}
Error Handling
javascript1class AddressServiceError extends Error {2 constructor(message, code, details = {}) {3 super(message);4 this.code = code;5 this.details = details;6 }7}89// Error codes10const ErrorCodes = {11 PARSE_FAILED: 'PARSE_FAILED',12 VALIDATION_FAILED: 'VALIDATION_FAILED',13 GEOCODE_FAILED: 'GEOCODE_FAILED',14 RATE_LIMITED: 'RATE_LIMITED',15 API_ERROR: 'API_ERROR'16};
Rate Limiting Strategy
javascript1class RateLimiter {2 constructor(requestsPerSecond = 1) {3 this.minInterval = 1000 / requestsPerSecond;4 this.lastRequest = 0;5 }67 async wait() {8 const now = Date.now();9 const elapsed = now - this.lastRequest;10 const wait = Math.max(0, this.minInterval - elapsed);1112 if (wait > 0) {13 await new Promise(r => setTimeout(r, wait));14 }1516 this.lastRequest = Date.now();17 }18}
Caching Strategy
javascript1class AddressCache {2 constructor(ttlMs = 24 * 60 * 60 * 1000) { // 24 hours3 this.cache = new Map();4 this.ttl = ttlMs;5 }67 generateKey(address) {8 const normalized = JSON.stringify(address).toLowerCase();9 return this.hash(normalized);10 }1112 get(address) {13 const key = this.generateKey(address);14 const entry = this.cache.get(key);1516 if (!entry) return null;17 if (Date.now() > entry.expires) {18 this.cache.delete(key);19 return null;20 }2122 return entry.value;23 }2425 set(address, value) {26 const key = this.generateKey(address);27 this.cache.set(key, {28 value,29 expires: Date.now() + this.ttl30 });31 }32}
Testing Strategy
- Unit Tests: Test each component in isolation
- Integration Tests: Test component interactions
- Mock External APIs: Use mocks for GeoNames/Nominatim
- Edge Cases: International addresses, malformed input
Project Requirements
Your final project must:
- Parse addresses for at least 3 countries (US, UK, DE)
- Validate postal codes against format rules
- Normalize street types and directionals
- Geocode addresses using Nominatim
- Detect duplicate addresses with fuzzy matching
- Handle rate limiting gracefully
- Cache results to minimize API calls
- Include comprehensive error handling
- Pass all provided unit tests
Success Criteria
| Requirement | Points |
|---|---|
| Address parsing (3 countries) | 20 |
| Postal code validation | 15 |
| Address normalization | 15 |
| Geocoding integration | 15 |
| Deduplication | 15 |
| Error handling | 10 |
| Caching | 10 |
| Total | 100 |