Building a Django Package — Part 3: Health Check Manager and Endpoint
In Part 3, we connect the database and cache health checks, build the health check manager, expose a Django health endpoint, measure check latency, and return a structured health status.
Django
Packages
OpenSource
Python
Aug 23, 2026 · 7 min read
In the previous part, we implemented the first two health checks for django-healthkit: database connectivity and Django cache functionality. Both checks are intentionally small and independent, but a Django application shouldn't need to call each check manually. Instead, we want a single endpoint such as GET /health/ that runs all configured checks and returns one consistent response.
From Individual Checks to One Health Check
Our current architecture contains individual checks such as check_database() and check_cache(). The next step is to introduce a layer that coordinates these checks. The health check manager should load configured checks, execute them, measure their execution time, collect their results, and determine the overall health status.
The architecture becomes:
Django Application
│
▼
Health Check Manager
│
├── Database Check
│
└── Cache Check
The individual checks should remain focused on one thing: checking whether a specific dependency is working. The manager is responsible for coordinating them.
A Common Interface for Checks
As the package grows, we may eventually have checks for the database, cache, disk, memory, CPU, Redis, Celery, and external APIs. A common interface makes these checks easier to manage. Each check can expose a simple method that performs the check and returns a consistent result.
For example:
class DatabaseCheck:
def check(self):
...
The manager doesn't need to know how the database check works. It only needs to know that the check can be executed and that it returns a result.
Designing the Check Result
A useful health check should return more than a simple boolean. A result can contain the check name, its health status, a human-readable message, and execution latency.
{
"name": "database",
"healthy": True,
"message": "Database is healthy.",
"latency": 0.49,
}
This gives monitoring systems a simple status while also providing developers with useful diagnostic information.
Measuring Latency
The manager can measure how long each check takes using time.perf_counter():
import time
start = time.perf_counter()
result = check.check()
latency = time.perf_counter() - start
Latency is useful because a dependency can technically be available while becoming significantly slower than usual. Keeping latency measurement in the manager also means individual checks can stay focused on their actual responsibility.
Running Multiple Checks
The manager can iterate over all configured checks and execute them one by one. Conceptually, the flow looks like this:
def run_checks(checks):
results = []
for check in checks:
start = time.perf_counter()
result = check.check()
latency = time.perf_counter() - start
result["latency"] = latency
results.append(result)
return results
The important part is that the manager doesn't need to know whether a check uses Django's database, cache, filesystem, Redis, Celery, or an external API. It simply executes the configured check.
Calculating the Overall Health
After all checks have been executed, we need to determine the overall application health. If every check succeeds, the application is healthy. If one of the required checks fails, the overall result should indicate that the application is unhealthy.
A simple rule is:
healthy = all(
result["healthy"]
for result in results
)
This gives us one value that represents the state of the entire health-check system.
The Final Response
A healthy response can look like this:
{
"healthy": true,
"checks": [
{
"name": "database",
"healthy": true,
"message": "Database is healthy.",
"latency": 0.49
},
{
"name": "cache",
"healthy": true,
"message": "Cache is healthy.",
"latency": 0.57
}
]
}
If the cache fails, the response can clearly identify the failing dependency:
{
"healthy": false,
"checks": [
{
"name": "database",
"healthy": true,
"message": "Database is healthy.",
"latency": 0.49
},
{
"name": "cache",
"healthy": false,
"message": "Cache is unhealthy.",
"latency": 1.23
}
]
}
This is much more useful than returning a generic error because we can see both the overall status and the state of each individual dependency.
Configuring the Checks
The checks should not be hard-coded into the manager. Instead, they can be configured through Django settings:
HEALTHKIT = {
"CHECKS": [
"django_healthkit.checks.database.DatabaseCheck",
"django_healthkit.checks.cache.CacheCheck",
],
}
This allows each Django project to decide which checks it wants to enable. Later, additional checks can be added without changing the core manager.
Building the Health View
Now that the manager can execute the checks, we need to expose the result through Django. The package provides a class-based HealthView that acts as the HTTP layer between the request and the health-check system.
The flow is:
HTTP Request
↓
HealthView
↓
Health Check Manager
↓
Health Result
↓
JSON Response
The view should not contain database-checking or cache-checking logic. Its responsibility is to connect HTTP requests with the health-check system.
Registering the Endpoint
The endpoint can then be registered in the Django project's URL configuration:
from django.urls import path
from django_healthkit.views import HealthView
urlpatterns = [
path("health/", HealthView.as_view(), name="health"),
]
The application now exposes:
GET /health/
This endpoint can be called by developers, monitoring systems, load balancers, and container infrastructure.
HTTP Status Codes
A health endpoint should communicate health through HTTP status codes as well as JSON. A healthy application can return HTTP 200 OK, while an unhealthy application can return HTTP 503 Service Unavailable.
This makes the endpoint useful for infrastructure that only needs to check the HTTP status:
200 → healthy
503 → unhealthy
The JSON response can still provide the detailed information about individual checks.
Testing the Endpoint
The individual checks were already tested in Part 2. Now we should test their integration through the endpoint.
A basic test should verify that the endpoint is reachable and returns a healthy response when the dependencies are working:
def test_health_endpoint(client):
response = client.get("/health/")
assert response.status_code == 200
data = response.json()
assert data["healthy"] is True
assert len(data["checks"]) > 0
We should also test failure scenarios. If a dependency becomes unavailable, the check should report the failure without crashing the Django application, and the overall health result should become unhealthy.
Why the Endpoint Should Be Lightweight
Health endpoints are often called frequently, sometimes every few seconds or every minute. Because of this, health checks should remain lightweight. A database check shouldn't execute an expensive query, a cache check shouldn't write a large object, and a disk check shouldn't scan the entire filesystem.
The purpose of a health check is not to benchmark the application. It should simply answer whether the application and its critical dependencies are working well enough to serve requests.
The Architecture So Far
After this part, the package has a clear separation of responsibilities:
/health/
│
▼
HealthView
│
▼
Health Check Manager
│
┌─────────┴─────────┐
▼ ▼
DatabaseCheck CacheCheck
│ │
└─────────┬─────────┘
▼
Combined Result
│
▼
JSON Response
The checks know how to check individual dependencies, the manager knows how to execute and combine them, the view exposes the result over HTTP, and Django settings control which checks are enabled.
What's Next?
At this point, the core functionality of django-healthkit is in place. We can define health checks, run multiple checks, collect their results, measure latency, determine overall health, and expose the result through Django.
But we have built the package locally. Now we need to turn it into something other developers can actually install. In the final part, we will take django-healthkit through the complete release process: build the distribution, validate it, test it with TestPyPI, publish it to PyPI, create a Git tag, and create a GitHub Release.
Related Notes
Building a Django Package — Part 2: Database and Cache Health Checks
In this part, we implement the first health checks for django-healthkit, covering database connectivity and Django cache functionality with simple, independent, and testable checks.
Building a Django Package — Part 1: Setting Up django-healthkit
In this part, we build the initial structure of django-healthkit, configure the package with pyproject.toml, and prepare it for development.
Building a Secure Webhook Receiver for Server-to-Server Communication
How I built a secure FastAPI webhook receiver using RSA signatures to enable authenticated server-to-server communication, proxy requests, and connect applications across different network environments.
Why I Switched to Conventional Git Commit Messages
Why I switched from inconsistent Git commit messages to a Conventional Commits style, with practical examples for cleaner and more maintainable Git history.
Building django-healthkit
How a simple health endpoint for Bidar turned into django-healthkit, a lightweight Django health-check package.
Designing a Gold Jewelry E-Commerce Database with Django
Learn how to design a scalable Django database for a gold jewelry e-commerce platform by modeling products, attributes, and purchasable product variants using real-world domain-driven design principles.
How to Fix Common Next.js 16 Build Errors (Proxy, Dynamic Rendering & Revalidation)
Learn how to fix common Next.js 16 build errors, understand Proxy, Dynamic Rendering, and Revalidation, and improve your application's SEO and performance.
Why I Built cyber-ui: A Minimal Design System for Developers
How my projects, from backend systems to monitoring tools, led me to create a minimal and personal UI foundation.