Movie Search API: Filtering With HTML Form Interface
In this comprehensive guide, we'll delve into how to add a robust movie search and filtering feature to an existing movie service, complete with a user-friendly HTML form interface. This enhancement will allow users to easily find their favorite films based on various criteria such as name, ID, and genre. We'll walk through the process of creating a new REST endpoint, designing the HTML form, handling edge cases, and ensuring the reliability of the implementation through thorough testing and documentation.
1. Designing the New /movies/search REST Endpoint
To begin, we need to design a new REST endpoint specifically for movie search and filtering. This endpoint, /movies/search, will be the heart of our new feature. It's crucial that this endpoint is designed to be both efficient and flexible, allowing users to search for movies using different parameters.
Implementing Query Parameters
The /movies/search endpoint should accept query parameters such as name, id, and genre. These parameters will allow users to filter movies based on their specific preferences. For instance, a user might want to search for movies with a specific name, or within a particular genre. By using query parameters, we can provide a flexible and intuitive search experience.
Handling Different Search Criteria
When implementing the search functionality, it's important to consider how to handle different search criteria. For example, a user might want to search for movies that contain a specific word in the title, or movies that belong to a specific genre. The endpoint should be able to handle these different scenarios and return the appropriate results.
Returning Matching Results
The endpoint should filter movies from the movie data and return matching results. This means that when a user submits a search query, the endpoint should search through the available movie data and return only the movies that match the specified criteria. The results should be returned in a clear and concise format, making it easy for users to find the movies they're looking for.
To ensure your movie search API is effective, consider the following:
- Efficiency: Implement efficient search algorithms to handle large datasets without performance degradation. This might involve indexing your movie data or using optimized search queries.
- Flexibility: Allow for multiple search parameters to be combined. For example, a user should be able to search for movies with a specific name and genre simultaneously.
- Accuracy: Ensure the search logic accurately matches user queries. This may involve handling case sensitivity, partial matches, and special characters.
Example Implementation
Here’s an example of how the /movies/search endpoint might be implemented in a Python Flask application:
from flask import Flask, request, jsonify
app = Flask(__name__)
movies = [
{"id": 1, "name": "Pirates of the Caribbean", "genre": "Action"},
{"id": 2, "name": "The Curse of the Black Pearl", "genre": "Adventure"},
{"id": 3, "name": "At World's End", "genre": "Action"},
]
@app.route('/movies/search', methods=['GET'])
def search_movies():
name = request.args.get('name')
genre = request.args.get('genre')
movie_id = request.args.get('id')
results = movies
if name:
results = [movie for movie in results if name.lower() in movie['name'].lower()]
if genre:
results = [movie for movie in results if genre.lower() == movie['genre'].lower()]
if movie_id:
try:
movie_id = int(movie_id)
results = [movie for movie in results if movie['id'] == movie_id]
except ValueError:
return jsonify({"error": "Invalid movie ID"}), 400
if not results:
return jsonify({"message": "No movies found"}), 404
return jsonify(results)
if __name__ == '__main__':
app.run(debug=True)
This example demonstrates how to handle query parameters and filter movies based on the provided criteria. It also includes error handling for invalid movie IDs and cases where no movies are found. Properly structuring your endpoint is crucial for a seamless movie search functionality.
2. Enhancing the HTML Response with a Search Form
Next, we'll enhance the HTML response to include a search form. This form will allow users to easily input their search criteria and submit it to the /movies/search endpoint.
Designing the Search Form
The search form should include input fields for name, ID, and genre. Each input field should be clearly labeled, making it easy for users to understand what information to enter. The form should also include a search button that, when clicked, sends the search query to the /movies/search endpoint.
Implementing the HTML Form
Here’s an example of how the HTML form might look:
<form id="searchForm">
<label for="name">Name:</label><br>
<input type="text" id="name" name="name"><br><br>
<label for="id">ID:</label><br>
<input type="number" id="id" name="id"><br><br>
<label for="genre">Genre:</label><br>
<input type="text" id="genre" name="genre"><br><br>
<button type="submit">Search</button>
</form>
<div id="searchResults"></div>
<script>
document.getElementById('searchForm').addEventListener('submit', function(event) {
event.preventDefault();
const name = document.getElementById('name').value;
const id = document.getElementById('id').value;
const genre = document.getElementById('genre').value;
let url = '/movies/search?';
if (name) url += `name=${name}&`;
if (id) url += `id=${id}&`;
if (genre) url += `genre=${genre}&`;
fetch(url)
.then(response => response.json())
.then(data => {
const searchResultsDiv = document.getElementById('searchResults');
searchResultsDiv.innerHTML = '';
if (data.length === 0) {
searchResultsDiv.innerHTML = '<p>No movies found</p>';
} else {
data.forEach(movie => {
searchResultsDiv.innerHTML += `<p>${movie.name} (${movie.genre})</p>`;
});
}
})
.catch(error => {
console.error('Error:', error);
const searchResultsDiv = document.getElementById('searchResults');
searchResultsDiv.innerHTML = '<p>Error fetching results</p>';
});
});
</script>
This HTML form includes input fields for name, ID, and genre, as well as a search button. When the form is submitted, the JavaScript code prevents the default form submission behavior and instead constructs a URL with the search parameters. It then uses the fetch API to send a request to the /movies/search endpoint and displays the results in the searchResults div. Integrating an HTML form interface significantly improves the user experience for movie searches.
Handling the Search Query
When the search button is clicked, the form should construct a URL with the search parameters and send a request to the /movies/search endpoint. The endpoint should then process the search query and return the matching results. The results should be displayed in a clear and concise format, making it easy for users to find the movies they're looking for.
By creating an intuitive HTML form, users can easily interact with the movie search API, making the entire process seamless and user-friendly.
3. Handling Edge Cases
It's important to handle edge cases such as empty search results and invalid parameters. This ensures that the movie search and filtering feature is robust and reliable.
Empty Search Results
When no movies match the search criteria, the endpoint should return an appropriate message to the user. This could be a simple message such as “No movies found.” or a more detailed message explaining why no movies were found.
Invalid Parameters
If the user provides invalid parameters, such as a non-numeric movie ID, the endpoint should return an error message. This helps prevent errors and ensures that the search functionality works as expected.
Example Error Handling
Here’s an example of how to handle empty search results and invalid parameters in the Python Flask application:
@app.route('/movies/search', methods=['GET'])
def search_movies():
name = request.args.get('name')
genre = request.args.get('genre')
movie_id = request.args.get('id')
results = movies
if name:
results = [movie for movie in results if name.lower() in movie['name'].lower()]
if genre:
results = [movie for movie in results if genre.lower() == movie['genre'].lower()]
if movie_id:
try:
movie_id = int(movie_id)
results = [movie for movie in results if movie['id'] == movie_id]
except ValueError:
return jsonify({"error": "Invalid movie ID"}), 400
if not results:
return jsonify({"message": "No movies found"}), 404
return jsonify(results)
In this example, if an invalid movie ID is provided, the endpoint returns a 400 error with a JSON message indicating the error. If no movies are found, the endpoint returns a 404 error with a message indicating that no movies were found. Proper error handling is crucial for a reliable movie search API.
Best Practices for Edge Case Handling
- Clear Error Messages: Provide clear and informative error messages to help users understand what went wrong and how to fix it.
- Status Codes: Use appropriate HTTP status codes to indicate the type of error (e.g., 400 for bad request, 404 for not found).
- Logging: Log errors for debugging and monitoring purposes.
4. Updating/Creating Documentation
Comprehensive documentation is essential for any API. It helps developers understand how to use the API and troubleshoot any issues that may arise.
Documenting the /movies/search Endpoint
The documentation should include a detailed description of the /movies/search endpoint, including the supported query parameters, the expected input format, and the output format. It should also include examples of how to use the endpoint.
Updating Existing Documentation
If there is existing documentation for the movie service, it should be updated to include the new /movies/search endpoint. This ensures that the documentation is complete and up-to-date.
Creating New Documentation
If there is no existing documentation, you'll need to create it. This should include an overview of the movie service, as well as detailed information about each endpoint. High-quality documentation ensures that your movie search API is easily understandable and usable.
Key Elements of API Documentation
- Endpoint Description: A clear description of what the endpoint does.
- Parameters: A list of accepted parameters, their types, and whether they are required.
- Request Examples: Examples of how to make requests to the endpoint.
- Response Examples: Examples of the expected responses.
- Error Codes: A list of possible error codes and their meanings.
5. Updating/Creating Unit Tests
Unit tests are crucial for ensuring that the movie search and filtering feature works as expected. They help catch bugs early in the development process and ensure that the code is reliable.
Testing the /movies/search Endpoint
Unit tests should be created to test the /movies/search endpoint. These tests should cover various scenarios, such as searching for movies by name, ID, and genre, as well as handling edge cases such as empty search results and invalid parameters.
Writing Effective Unit Tests
When writing unit tests, it's important to focus on testing individual components of the code. This makes it easier to identify and fix bugs. Each test should be small and focused, testing only one specific aspect of the code.
Example Unit Tests
Here’s an example of how unit tests might look for the /movies/search endpoint using Python's unittest framework:
import unittest
import json
from app import app
class TestMovieSearch(unittest.TestCase):
def setUp(self):
self.app = app.test_client()
self.app.testing = True
def test_search_by_name(self):
response = self.app.get('/movies/search?name=Pirates')
self.assertEqual(response.status_code, 200)
data = json.loads(response.get_data(as_text=True))
self.assertEqual(len(data), 1)
self.assertEqual(data[0]['name'], 'Pirates of the Caribbean')
def test_search_by_genre(self):
response = self.app.get('/movies/search?genre=Action')
self.assertEqual(response.status_code, 200)
data = json.loads(response.get_data(as_text=True))
self.assertEqual(len(data), 2)
def test_search_by_id(self):
response = self.app.get('/movies/search?id=1')
self.assertEqual(response.status_code, 200)
data = json.loads(response.get_data(as_text=True))
self.assertEqual(len(data), 1)
self.assertEqual(data[0]['id'], 1)
def test_search_no_results(self):
response = self.app.get('/movies/search?name=NonExistentMovie')
self.assertEqual(response.status_code, 404)
data = json.loads(response.get_data(as_text=True))
self.assertEqual(data['message'], 'No movies found')
def test_search_invalid_id(self):
response = self.app.get('/movies/search?id=invalid')
self.assertEqual(response.status_code, 400)
data = json.loads(response.get_data(as_text=True))
self.assertEqual(data['error'], 'Invalid movie ID')
if __name__ == '__main__':
unittest.main()
These tests cover various scenarios, including searching by name, genre, and ID, as well as handling cases where no results are found or an invalid ID is provided. Comprehensive unit testing ensures the reliability and stability of your movie search API.
Best Practices for Unit Testing
- Test Driven Development (TDD): Write tests before writing the code to ensure that the code meets the requirements.
- Code Coverage: Aim for high code coverage to ensure that all parts of the code are tested.
- Continuous Integration: Integrate unit tests into a continuous integration pipeline to automatically run tests whenever changes are made to the code.
Conclusion
Adding a movie search and filtering feature to an existing movie service, complete with an HTML form interface, enhances the user experience and provides a powerful way for users to find their favorite films. By designing a new REST endpoint, creating an intuitive HTML form, handling edge cases, updating documentation, and implementing thorough unit tests, you can ensure that the feature is robust, reliable, and user-friendly. This comprehensive approach not only improves the functionality of the service but also ensures its long-term maintainability and scalability.
For further reading on RESTful API design and best practices, check out this resource.