# ADR-002: Hybrid API Design Pattern ## Status Accepted ## Context ThrillWiki serves two types of clients: 1. **Web browsers**: Need HTML responses for rendering pages 2. **API clients**: Need JSON responses for mobile apps and integrations We needed to decide how to handle these different client types efficiently without duplicating business logic. ## Decision We implemented a **Hybrid API Design Pattern** where views can serve both HTML and JSON responses based on content negotiation. ### Implementation Strategy 1. **API-first approach**: All business logic is implemented in serializers and services 2. **Content negotiation**: Views check request headers to determine response format 3. **Dual endpoints**: Some resources are available at both web and API URLs 4. **Shared serializers**: Same serializers used for both API responses and template context ### URL Structure ``` # Web URLs (HTML responses) /parks/ # Park list page /parks/cedar-point/ # Park detail page # API URLs (JSON responses) /api/v1/parks/ # Park list (JSON) /api/v1/parks/cedar-point/ # Park detail (JSON) ``` ### View Implementation ```python class HybridViewMixin: """ Mixin that enables views to serve both HTML and JSON responses. """ serializer_class = None def get_response_format(self, request): """Determine response format from Accept header or query param.""" if request.htmx: return 'html' if 'application/json' in request.headers.get('Accept', ''): return 'json' if request.GET.get('format') == 'json': return 'json' return 'html' def render_response(self, request, context, **kwargs): """Render appropriate response based on format.""" format = self.get_response_format(request) if format == 'json': serializer = self.serializer_class(context['object']) return JsonResponse(serializer.data) return super().render_to_response(context, **kwargs) ``` ### Serializer Patterns ```python # API serializers use camelCase for JSON responses class ParkSerializer(serializers.ModelSerializer): operatorName = serializers.CharField(source='operator.name') rideCount = serializers.IntegerField(source='ride_count') class Meta: model = Park fields = ['id', 'name', 'slug', 'operatorName', 'rideCount'] ``` ## Consequences ### Benefits 1. **Code Reuse**: Single set of business logic serves both web and API 2. **Consistency**: API and web views always return consistent data 3. **Flexibility**: Easy to add new response formats 4. **Progressive Enhancement**: API available without duplicating work 5. **Mobile-Ready**: API ready for mobile app development ### Trade-offs 1. **Complexity**: Views need to handle multiple response formats 2. **Testing**: Need to test both HTML and JSON responses 3. **Documentation**: Must document both web and API interfaces ### Response Format Decision | Request Type | Response Format | |-------------|-----------------| | HTMX request | HTML partial | | Browser (Accept: text/html) | Full HTML page | | API (Accept: application/json) | JSON | | Query param (?format=json) | JSON | ## Alternatives Considered ### Separate API and Web Views **Rejected because:** - Duplicate business logic - Risk of divergence between API and web - More code to maintain ### API-Only with JavaScript Frontend **Rejected because:** - Conflicts with ADR-001 (Django + HTMX architecture) - Poor SEO without SSR - Increased complexity ### GraphQL **Rejected because:** - Overkill for current requirements - Steeper learning curve - Less mature Django ecosystem support ## Implementation Details ### Hybrid Loader Services For complex data loading scenarios, we use hybrid loader services: ```python # backend/apps/parks/services/hybrid_loader.py class ParkHybridLoader: """ Loads park data optimized for both API and web contexts. """ def load_park_detail(self, slug, context='web'): park = Park.objects.optimized_for_detail().get(slug=slug) if context == 'api': return ParkSerializer(park).data return {'park': park} ``` ### API Versioning API endpoints are versioned to allow breaking changes: ``` /api/v1/parks/ # Current version /api/v2/parks/ # Future version (if needed) ``` ## References - [Django REST Framework](https://www.django-rest-framework.org/) - [Content Negotiation](https://www.django-rest-framework.org/api-guide/content-negotiation/) - [ThrillWiki API Documentation](../THRILLWIKI_API_DOCUMENTATION.md)