Skip to content

feat: health check endpoint and API versioning under /api/v1/#17

Merged
sadykovIsmail merged 1 commit intomainfrom
feat/health-check-versioning-issue-8
Mar 30, 2026
Merged

feat: health check endpoint and API versioning under /api/v1/#17
sadykovIsmail merged 1 commit intomainfrom
feat/health-check-versioning-issue-8

Conversation

@sadykovIsmail
Copy link
Copy Markdown
Owner

Summary

Adds a production-essential health check endpoint and versions all API routes under /api/v1/.

Health Check

GET /api/health/
  • No auth required — load balancers must be able to probe without credentials
  • Checks DB connectivity with SELECT 1
  • Returns 200 when healthy, 503 when DB is unreachable
  • Suitable for Kubernetes liveness/readiness probes
// 200 OK
{ "status": "ok", "database": "ok" }

// 503 Service Unavailable
{ "status": "degraded", "database": "unavailable" }

API Versioning

All routes now available under /api/v1/:

  • /api/v1/user/ → user endpoints
  • /api/v1/recipe/ → recipe endpoints

Old /api/user/ and /api/recipe/ kept as deprecated aliases so existing integrations don't break.

Test plan

  • GET /api/health/ returns 200 without auth
  • GET /api/v1/recipe/recipes/ works with JWT
  • GET /api/recipe/recipes/ still works (backwards-compat)
  • All tests pass

Closes #8

- Add health/ app with GET /api/health/ endpoint (no auth required)
  - Returns {status: ok, database: ok} on 200
  - Returns {status: degraded, database: unavailable} on 503 when DB is down
  - Suitable for k8s liveness/readiness probes and load balancer health checks
- Add health tests: happy path, unauthenticated access, DB failure simulation
- Version all API routes under /api/v1/user/ and /api/v1/recipe/
- Keep unversioned /api/user/ and /api/recipe/ as deprecated aliases for backwards compat
- Register health app in INSTALLED_APPS

Closes #8
Copilot AI review requested due to automatic review settings March 30, 2026 22:44
@sadykovIsmail sadykovIsmail merged commit f078ccd into main Mar 30, 2026
1 check failed
@sadykovIsmail sadykovIsmail deleted the feat/health-check-versioning-issue-8 branch March 30, 2026 22:44
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a lightweight health check endpoint for load balancer / Kubernetes probes and introduces /api/v1/ routing for existing APIs while keeping legacy unversioned routes as aliases.

Changes:

  • Added GET /api/health/ endpoint that performs a simple DB connectivity check and returns 200/503.
  • Added test coverage for the new health endpoint behavior.
  • Exposed all existing user and recipe routes under /api/v1/ and kept /api/* as deprecated aliases.

Reviewed changes

Copilot reviewed 4 out of 5 changed files in this pull request and generated 5 comments.

File Description
app/health/views.py Implements DRF health check view with DB probe and status payload.
app/health/tests.py Adds tests for health endpoint success, unauthenticated access, and DB-down behavior.
app/app/urls.py Wires in /api/health/ and introduces /api/v1/ route prefixes plus legacy aliases.
app/app/settings.py Registers the new health Django app.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread app/app/urls.py
Comment on lines +34 to 35
path('api/user/', include('user.urls')),
path('api/recipe/', include('recipe.urls')),
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Including user.urls and recipe.urls twice will register the same URL namespace (user / recipe) multiple times (those modules define app_name). This triggers Django's non-unique namespace check and can make reverse('user:...') / reverse('recipe:...') ambiguous or resolve unpredictably. Use distinct namespaces for the deprecated aliases (e.g., include('user.urls', namespace='user-legacy') / include('recipe.urls', namespace='recipe-legacy')) so the primary user: / recipe: names reverse to the v1 routes without duplication.

Suggested change
path('api/user/', include('user.urls')),
path('api/recipe/', include('recipe.urls')),
path('api/user/', include('user.urls', namespace='user-legacy')),
path('api/recipe/', include('recipe.urls', namespace='recipe-legacy')),

Copilot uses AI. Check for mistakes.
Comment thread app/app/settings.py
Comment on lines 70 to 74
'core',
'user',
'recipe',
'health',
]
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR/issue description mentions adding an API_VERSION setting, but no such setting is introduced. Consider adding something like API_VERSION = 'v1' in settings and using it to build the versioned URL prefixes (and related settings like SPECTACULAR_SETTINGS['SCHEMA_PATH_PREFIX']) to avoid hardcoding v1 in multiple places.

Copilot uses AI. Check for mistakes.
Comment thread app/health/views.py
Comment on lines +2 to +3
from django.db import connection, OperationalError

Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This imports OperationalError from django.db, but elsewhere in the codebase the convention is from django.db.utils import OperationalError (e.g., core/management/commands/wait_for_db.py). Importing from django.db.utils is the canonical location and avoids surprises across Django versions/backends.

Suggested change
from django.db import connection, OperationalError
from django.db import connection
from django.db.utils import OperationalError

Copilot uses AI. Check for mistakes.
Comment thread app/health/views.py

return Response(
{
'status': 'ok' if http_status == 200 else 'degraded',
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The response sets 'status' based on http_status == 200, which hardcodes the numeric status code even though http_status is already a DRF constant. Prefer comparing against status.HTTP_200_OK (or deriving the overall status from db_status) to avoid magic numbers and keep the logic self-explanatory.

Suggested change
'status': 'ok' if http_status == 200 else 'degraded',
'status': 'ok' if http_status == status.HTTP_200_OK else 'degraded',

Copilot uses AI. Check for mistakes.
Comment thread app/health/tests.py

def test_health_check_db_down_returns_503(self):
"""When the database is unreachable the endpoint returns 503."""
from django.db import OperationalError
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For consistency with the rest of the repo (e.g., core/tests/test_commands.py), import OperationalError from django.db.utils rather than django.db in this test as well.

Suggested change
from django.db import OperationalError
from django.db.utils import OperationalError

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: health check endpoint and API versioning

2 participants