If you’ve worked with APIs in Python, you’re likely familiar with the requests library. But what if you could get the same ease of use, asynchronous support, HTTP/2 performance gains, and advanced configuration options? That’s HTTPX exactly what offers.
HTTPXis a powerful HTTP client that supports synchronous and asynchronous requests, making it an excellent choice for handling API interactions at any scale.
It provides built-in authentication, connection pooling, and streaming responses, making working with modern web services easier.
This guide will walk you through integrating HTTPX into your Python projects and maximizing its features to handle HTTP requests efficiently.
Prerequisites
Before diving in, install Python 3.13 or higher. While this guide assumes some familiarity with making HTTP requests in Python—especially with the requests library—you can still follow along even if you're new to it.
Getting started with HTTPX
For the best learning experience, create a new Python project to experiment with the concepts covered in this tutorial.
Start by setting up a new directory and initializing a virtual environment:
Activate the virtual environment:
Following that, install the latest version of httpx with the command below:
Create a new client.py file in the root of your project directory and add the following code:
This snippet imports the httpx package and defines a simple function that makes a GET request to a test endpoint.
Let's go ahead and run this script:
You should observe output similar to the following:
The response from httpbin.org contains information about our request, including the sent headers. This simple example demonstrates the ease of making HTTP requests with HTTPX.
Understanding HTTPX response objects
When you make an HTTP request using httpx, the response object contains important information about the server’s reply. This includes the status code, headers, and response body, which help determine whether the request was successful and how to process the returned data.
Modify your client.py file to inspect different parts of the response:
This script retrieves data from httpbin.org and displays important response details. Here’s what each part of the response object represents:
response.status_code– Indicates the HTTP status of the request (e.g.,200for success,404for not found).response.headers– A dictionary containing metadata about the response, such as content type, server information, and caching policies.response.text– The full response body returned as a string.response.json()– Parses the response body as JSON, converting it into a Python dictionary for easy data manipulation.
After running the script, you should see something like:
The response output consists of three key parts:
- Status Code – Indicates whether the request was successful. A
200status means success, while a404suggests that the requested resource was not found, and a500points to a server error. - Headers – Provide metadata about the response, including details like
Content-Type, which specifies the format of the response, andServer, which identifies the web server handling the request. - Content – Contains the actual response body, typically a JSON object that includes valuable details about the request, such as the headers sent and the request's origin.
Now that you understand response objects, let's explore how to make different types of HTTP requests using HTTPX.
Understanding basic request methods
Now that you understand how HTTPX handles responses, let's explore different types of HTTP requests beyond GET.
HTTPX supports a variety of methods, including POST, PUT, DELETE, and query parameters with GET, allowing you to interact with APIs that require data submission or modifications.
A POST request is typically used to send data to a server, such as creating a new resource or submitting a form.
Remove all contents from client.py and replace it with the following code to send a POST request with JSON data:
Run the script:
You should see output similar to this:
The response confirms that your request was processed successfully. The json field in the response body contains the data you sent, proving that the server received it correctly.
The headers also indicate that the request was sent as application/json, thanks to HTTPX automatically setting the correct content type.
While POST is used to create new resources, a PUT request is used to update existing ones. When you need to modify an entry rather than add a new one, PUT ensures that the existing data is replaced with the updated values.
Here’s an example of making a PUT request with HTTPX:
This request updates an existing resource with new data, ensuring changes are correctly reflected on the server.
If you need to remove a resource instead, a DELETE request is used. This is particularly useful for deleting records from a database or removing items from an API:
Handling query parameters in a GET request
Sometimes, API requests need additional information in the URL, such as filtering or searching for specific data. This is done using query parameters.
Modify client.py to send a GET request with query parameters:
Run the script:
You should see output like:
The "args" field in the response confirms that the query parameter status=completed was sent successfully. This is useful when filtering or searching through API results.
Now that you've learned how to send different HTTP requests, the next step is to ensure your requests are resilient.
Handling timeouts and retries in HTTPX
Network issues, slow server responses, or connection failures can impact your application's performance. HTTPX provides built-in support for timeouts, retries, and error handling, which helps ensure your requests remain stable and reliable.
Setting a timeout for requests
By default, HTTPX waits indefinitely for a response, which can cause your program to hang if the server is slow or unresponsive.
You can prevent this by setting a timeout, which defines how long HTTPX should wait before giving up on a request.
Replace all the contents in the client.py file with the following to implement a timeout:
If the server responds within 3 seconds, the request succeeds, and the response is printed. If the server takes longer than 3 seconds, HTTPX raises a TimeoutException, and the program prints "Request timed out!".
Run the script:
If the request exceeds the timeout, you'll see:
This is useful for preventing unresponsive requests from blocking your application.
Retrying failed requests
Sometimes, network failures or temporary server issues cause requests to fail. HTTPX allows you to retry failed requests automatically using an httpx.Client with a custom retry strategy.
Remove all existing code in the client.py file and update it with the following to include retries:
The script attempts to send a request to an endpoint that returns a 500 error. If an error occurs, it retries up to 3 times before giving up.
The raise_for_status() method ensures we only process successful responses.
If all retry attempts fail, it prints "All retry attempts failed."
Run the script:
Expected output:
Implementing retries improves your application's resilience to temporary network issues.
Working with authentication in HTTPX
Now that you have learned how to handle timeouts and retries in HTTPX, the next step is securing API requests. Many APIs require authentication before allowing access to protected resources. HTTPX supports various authentication methods, such as Basic Auth, Token-based authentication, and OAuth.
Using basic authentication
Basic Authentication (Basic Auth) is one of the simplest forms of authentication, where the client sends a username and password encoded in the request headers.
Replace the contents of client.py with the following code to use Basic Authentication:
The auth argument accepts a tuple containing the username and password. HTTPX automatically encodes the credentials into the Authorization header as Basic base64(username:password).
Then the server verifies the credentials and responds accordingly.
Run the script with the following:
Expected output:
This confirms that the authentication was successful. If the credentials are incorrect, the server will return a 401 Unauthorized response.
Using Bearer Token authentication
Many APIs use token-based authentication (e.g., API keys, JWT tokens) instead of Basic Auth. Tokens are typically passed in the Authorization header.
Modify client.py to send an API request with a Bearer Token:
The Authorization header is manually set to "Bearer <token>".
The server verifies the token and responds accordingly.
Run the script like you have been doing:
Expected output:
If the token is missing or invalid, you'll receive a 403 Forbidden or 401 Unauthorized response.
Asynchronous requests with HTTPX
So far, you've been making synchronous HTTP requests, meaning each request blocks the program until it responds. This is fine for small-scale applications, but asynchronous programming can significantly improve performance when dealing with multiple API calls or high-latency networks.
HTTPX provides native async support using Python’s asyncio and async/await syntax, allowing requests to be made concurrently instead of sequentially.
Asynchronous requests are beneficial when:
- You need to make multiple API calls concurrently (e.g., fetching data from multiple endpoints).
- Your application handles high-latency network requests.
- You want to improve efficiency without blocking execution.
Instead of waiting for each request to finish before making the next one, async HTTPX allows multiple requests to be sent simultaneously, significantly reducing wait times.
Making an asynchronous GET request
Replace the contents of client.py with the following to send an asynchronous GET request:
The fetch_data() function is an asynchronous coroutine that makes a non-blocking GET request.
The async with httpx.AsyncClient() as client statement ensures proper session management, while await client.get(url) sends the request without pausing execution.
Finally, asyncio.run(fetch_data()) starts the event loop, allowing multiple API calls to run concurrently for improved performance.
Run the script:
Expected output:
The output confirms a successful async request with Status Code: 200, displaying request metadata, source IP, and the requested URL—showcasing non-blocking execution.
Making multiple asynchronous requests
One of the most significant advantages of async requests is that they allow multiple API calls to be made concurrently. Instead of sending them individually, all requests are sent simultaneously, reducing total execution time.
Modify client.py to send multiple GET requests concurrently:
This program defines fetch_url(), an asynchronous function that requests a GET to a given URL and returns its response status. The main() function builds a list of URLs and sends all requests concurrently using asyncio.gather(*tasks), ensuring they run in parallel.
An AsyncClient is used within a context manager to manage connections efficiently. By executing all requests simultaneously, the program avoids delays in sequential execution.
Rerun the program and you will see output like this:
In a synchronous approach, each request would complete before the next one starts, leading to a total execution time roughly equal to the sum of all delays.
However, with async execution, all requests run simultaneously, drastically reducing the overall wait time.
This method is handy when working with APIs involving network latency or when handling high requests efficiently.
Leveraging HTTP/2 with HTTPX
HTTP/2 is a major iteration of the HTTP protocol that provides a far more efficient transport layer with significant performance benefits. Unlike HTTP/1.1's text-based format, HTTP/2 uses a binary format that enables:
- Multiplexing: Multiple requests and responses can share a single TCP connection simultaneously
- Header compression: Reduces overhead by efficiently compressing HTTP headers
- Stream prioritization: Allows clients to indicate which resources are more important
- Server push: Enables servers to proactively send resources to clients before they're explicitly requested
These improvements can dramatically enhance performance, especially for applications making multiple concurrent requests.
HTTPX doesn't enable HTTP/2 by default, as HTTP/1.1 is considered the more mature and battle-tested option. To use HTTP/2, you first need to install the required dependencies:
Once installed, you can enable HTTP/2 by setting the http2 parameter to True when creating a client:
When you run this script, you should see output like this:
It's important to note that enabling HTTP/2 in your HTTPX client does not guarantee that your requests will use HTTP/2. Both the client and server must support HTTP/2 for it to be used.
If you connect to a server that only supports HTTP/1.1, the client will automatically fall back to using HTTP/1.1.
You can always check which version of the HTTP protocol was actually used by examining the .http_version property on the response:
Running this script will show which HTTP version was used for each request:
This demonstrates how HTTPX automatically selects the appropriate protocol based on server support.
Final thoughts
This guide covered HTTPX's key features, from basic requests to advanced capabilities like authentication, async operations, and HTTP/2 support. HTTPX bridges the gap between the familiar requests library API and modern Python development needs.
Its intuitive interface and powerful features make HTTPX suitable for everything from simple scripts to complex service architectures.
For more details, refer to the official HTTPX documentation. Happy coding!