UNPKG

@lineai/linkedin-api

Version:

Professional LinkedIn API client with TypeScript support, entity classes, and developer-friendly features. Perfect for AI coders, recruiting, lead generation, market research, and content analysis. Includes comprehensive JSDoc, helper constants (LOCATIONS

386 lines (315 loc) • 11.6 kB
# API Data Implementation Guide ## Overview This guide documents the process used to create the LinkedIn API package, serving as a blueprint for future API implementations. It captures the methodology, decisions, and patterns that lead to a developer-friendly npm package. ## šŸ†• v1.3.1 Critical Fix ### Location Parameter Type Correction - **Fixed LocationId Type**: Changed from `string & { __brand: 'LocationId' }` to `number & { __brand: 'LocationId' }` - **Updated LOCATIONS Constants**: All values now correctly typed as numbers (e.g., `102277331` not `"102277331"`) - **API Compatibility**: Eliminates "Payload is incorrect" errors when using location filters - **Cross-Method Support**: Both profile search (GET with joined strings) and company search (POST with arrays) work correctly ```typescript // Before v1.3.1 (BROKEN) type LocationId = string & { __brand: 'LocationId' }; export const LOCATIONS = { US: { SAN_FRANCISCO: "102277331" } // String caused API errors }; // v1.3.1+ (FIXED) type LocationId = number & { __brand: 'LocationId' }; export const LOCATIONS = { US: { SAN_FRANCISCO: 102277331 } // Number works correctly }; ``` ## v1.3.0 Technical Improvements ### Type Safety & Runtime Error Prevention - **Added RapidAPI Response Types**: Complete TypeScript interfaces for all API responses - **Eliminated shared callEndpoint**: Each method now makes direct, typed HTTP calls - **Fixed data path bugs**: Prevented `response.data.data.items` vs `response.data.items` errors - **TypeScript interfaces**: `RapidApiProfileSearchResponse`, `RapidApiCompanySearchResponse`, etc. ### Parameter Immutability Pattern ```javascript // Before v1.3.0: Parameters were mutated delete params.minItems; // Modified original object // v1.3.0+: Defensive copying const apiParams = { ...params }; delete apiParams.minItems; // Original params preserved return new Result(data, this, params); // Store original ``` ### Enhanced Caching Implementation - **Universal caching**: All `get` methods now implement 15-minute cache - **Cache keys**: Format `{method}:{param}` (e.g., `profile:satyanadella`) - **Performance**: 100% cache hit reduction in response time - **Automatic expiration**: Built-in 15-minute timeout ## Process Steps ### 1. Analyze Real API Data **Critical first step: Always start with actual API responses** - Collect real JSON responses for all endpoints (get, search, etc.) - Store example responses in organized directories (e.g., `/profile`, `/company`, `/posts`) - Compare schema documentation with actual data - **trust the data over docs** - Identify patterns: - Which fields can be null, empty strings, or missing entirely - Differences between search results (limited data) vs full entity responses - Nested data structures and their consistency - Date/time formats used ### 2. Design User-Friendly Interfaces **Goal: Hide complexity while maintaining flexibility** #### Separate Search vs Full Data - Search results return limited data (e.g., name, headline, URL) - Full entities contain complete nested structures - Create separate TypeScript interfaces for each #### Entity Classes Pattern ```typescript // Encapsulate complex data access class LinkedInProfile { constructor(private data: ProfileData) {} // Simple getters with safe defaults getFullName(): string { return `${this.data.firstName || ''} ${this.data.lastName || ''}`.trim() || 'Unknown'; } // Helper methods for computed values getCurrentPosition(): Position | null { return this.data.position?.[0] || null; } // Raw data escape hatch get raw(): ProfileData { return this.data; } } ``` #### Enable Seamless Data Loading ```typescript // Search item can load full data class ProfileSearchItem { async loadFullProfile(): Promise<LinkedInProfile> { return this.api.getProfile(this.username); } } ``` ### 3. Implement Type Safety **TypeScript as a first-class citizen** #### Branded Types for IDs ```typescript type LocationId = string & { __brand: 'LocationId' }; type CompanyId = string & { __brand: 'CompanyId' }; // Prevents mixing up different ID types function searchByLocation(locationId: LocationId) { ... } ``` #### Comprehensive Type Definitions - Export all interfaces for consumer use - Create `.d.ts` file with complete type definitions - Use JSDoc comments for all public methods - Include examples in documentation ### 4. Add Developer Experience Enhancements #### Helper Constants ```typescript export const LOCATIONS = { US: { NEW_YORK: '105080838' as LocationId, SAN_FRANCISCO: '102277331' as LocationId, // ... more cities } }; // Usage: api.searchProfiles({ geo: [LOCATIONS.US.NEW_YORK] }) ``` #### Smart Parameter Handling ```typescript // Accept arrays and auto-join for API searchProfiles(params: { geo?: LocationId[] | string; // Accept array }) { if (Array.isArray(params.geo)) { params.geo = params.geo.join(','); // API expects comma-separated } } ``` #### Consistent Data Access - Always return Date objects for timestamps - Always return arrays (never null/undefined) - Provide sensible defaults for missing data - Construct URLs when missing ### 5. Handle Common Pain Points #### Rate Limiting ```typescript // Expose rate limit info interface ApiResponse<T> { data: T; rateLimit: { remaining: number; reset: Date; }; } // Typed errors for handling class RateLimitError extends Error { retryAfter: Date; } ``` #### Pagination Helpers ```typescript class SearchResults { hasNextPage(): boolean { ... } async getNextPage(): Promise<SearchResults> { ... } } ``` #### Safe Defaults - Return empty arrays instead of null/undefined - Provide default avatar URLs for missing profile pictures - Return 'Unknown' for missing names - Return 0 for missing counts #### Simple Caching - Cache GET requests for 5 minutes - Reduce unnecessary API calls - Make it optional/configurable ### 6. Package Structure ``` linkedin-api/ ā”œā”€ā”€ index.js # Main implementation ā”œā”€ā”€ index.d.ts # TypeScript definitions ā”œā”€ā”€ API_IMPLEMENTATION_GUIDE.md # This guide ā”œā”€ā”€ README.md # User documentation ā”œā”€ā”€ package.json # NPM configuration └── test-data/ # Real API responses ā”œā”€ā”€ profile/ ā”œā”€ā”€ company/ └── posts/ ``` ## Key Principles ### 1. **Start with Real Data** Never trust documentation alone. Always work from actual API responses to understand the true data structure. ### 2. **Hide Complexity** Entity classes should make complex nested data feel simple. Developers shouldn't need to know the API structure. ### 3. **Type Everything** Full TypeScript support is non-negotiable. It prevents errors and provides excellent IDE support. ### 4. **Fail Gracefully** - Safe defaults prevent runtime errors - Clear, typed error messages - Never throw on missing optional data ### 5. **Developer First** Every decision should optimize for the consuming developer's experience: - Intuitive method names - Minimal configuration - Sensible defaults - Escape hatches for advanced use ## Common Patterns ### Entity Classes ```typescript class Entity { constructor(private data: RawData) {} // Simple getters getName(): string { return this.data.name || 'Unknown'; } // Computed properties getDisplayName(): string { return this.name || this.username; } // Safe array access getItems(): Item[] { return this.data.items || []; } // Date handling getCreatedAt(): Date { return new Date(this.data.timestamp); } // Raw data access get raw(): RawData { return this.data; } } ``` ### Search Results ```typescript class SearchResult { constructor( private data: any, private api: ApiClient, private params: SearchParams ) {} get items(): SearchItem[] { ... } get total(): number { ... } hasNextPage(): boolean { ... } async getNextPage(): Promise<SearchResult> { ... } } ``` ### Error Handling ```typescript // Specific error types export class ApiError extends Error { constructor(message: string, public code: string) { super(message); } } export class NotFoundError extends ApiError { constructor(resource: string) { super(`${resource} not found`, 'NOT_FOUND'); } } // Usage try { const profile = await api.getProfile('username'); } catch (error) { if (error instanceof NotFoundError) { // Handle not found } else if (error instanceof RateLimitError) { // Wait and retry } } ``` ### Helper Functions ```typescript // Validation export function isValidUsername(username: string): boolean { return /^[a-zA-Z0-9-]{3,100}$/.test(username); } // Extraction export function extractUsername(url: string): string | null { const match = url.match(/linkedin\.com\/in\/([a-zA-Z0-9-]+)/); return match?.[1] || null; } // Defaults export const DEFAULT_AVATAR = 'https://cdn.linkedin.com/default-profile.png'; ``` ## Implementation Checklist ### Planning Phase - [ ] Collect real API response examples - [ ] Identify all data variations and edge cases - [ ] Design entity class interfaces - [ ] Plan helper constants and utilities - [ ] Consider pagination patterns - [ ] Plan error handling strategy ### Development Phase - [ ] Create TypeScript interfaces from real data - [ ] Implement entity classes with safe defaults - [ ] Add helper methods for common operations - [ ] Create search result classes with pagination - [ ] Implement proper error types - [ ] Add caching layer (if applicable) - [ ] Create helper constants - [ ] Write comprehensive JSDoc comments ### Testing Phase - [ ] Test with real API responses - [ ] Verify null/undefined handling - [ ] Test pagination edge cases - [ ] Verify TypeScript types are accurate - [ ] Test error scenarios - [ ] Ensure documentation examples are accurate ### Documentation Phase - [ ] Write clear README with inline code examples - [ ] Document all public methods - [ ] Include common use cases - [ ] Document error handling - [ ] Add troubleshooting section ### Publishing Phase - [ ] Set correct package.json fields - [ ] Include only necessary files - [ ] Test npm installation - [ ] Verify TypeScript definitions work - [ ] Tag version appropriately ## Lessons Learned ### What Works Well 1. **Entity classes** - Dramatically simplify data access 2. **Branded types** - Prevent ID mix-ups at compile time 3. **Safe defaults** - Eliminate most runtime errors 4. **Search → Full data flow** - Intuitive for developers 5. **Helper constants** - Remove magic strings/numbers ### Common Pitfalls to Avoid 1. **Trusting documentation over real data** - Always verify 2. **Exposing raw API complexity** - Abstract it away 3. **Forgetting edge cases** - Empty strings, nulls, missing fields 4. **Inconsistent error handling** - Use typed errors 5. **Missing pagination helpers** - Makes the API painful to use ### Future Improvements 1. **Response validation** - Runtime type checking with zod/joi 2. **Retry strategies** - Exponential backoff for failures 3. **Request queuing** - Respect rate limits proactively 4. **Streaming responses** - For large data sets 5. **Webhook support** - For real-time updates ## Conclusion This guide represents a battle-tested approach to creating developer-friendly API packages. By starting with real data, hiding complexity behind intuitive interfaces, and prioritizing the developer experience, we create packages that are both powerful and pleasant to use. Remember: The best API package is one where developers never need to read the original API documentation.