Project: REST API Blog
Introduction
This project exposes the blog domain as a versioned JSON API under /api/v1/ using Django REST framework. Public clients read published posts; authenticated users create and edit their own posts with Token authentication. You validate with curl or Postman and optionally connect a separate frontend—patterns from DRF basics and API auth/CORS applied end to end.
Prerequisites
- Project: Full-Stack Blog (MTV) — or equivalent
Postmodel - Django REST Framework Basics
- API Authentication, CORS, and Frontend Integration
- Testing Django Applications
Project Goals
| Method | Endpoint | Auth | Action |
|---|---|---|---|
| GET | /api/v1/posts/ | Public | List published posts (paginated) |
| GET | /api/v1/posts/{id}/ | Public | Post detail |
| POST | /api/v1/posts/ | Token | Create post |
| PUT/PATCH | /api/v1/posts/{id}/ | Token + author | Update own post |
| DELETE | /api/v1/posts/{id}/ | Token + author | Delete own post |
| POST | /api/v1/auth/token/ | Credentials | Obtain token |
Optional: filter by ?category=news, ?search=django.
Step 1: Install and Settings
pip install djangorestframework django-filter django-cors-headersconfig/settings/base.py:
python manage.py migrateStep 2: Serializers
blog/serializers.py:
Use lighter serializer on list, full on detail—common DRF pattern.
Step 3: Permissions
blog/permissions.py:
from rest_framework.permissions import BasePermission, SAFE_METHODS
class IsAuthorOrReadOnly(BasePermission):
def has_object_permission(self, request, view, obj):
if request.method in SAFE_METHODS:
return True
return obj.author == request.userStep 4: ViewSet
blog/viewsets.py:
Step 5: URL Routing
blog/urls_api.py:
config/urls.py:
urlpatterns = [
path("admin/", admin.site.urls),
path("api/v1/", include("blog.urls_api")),
path("", include("blog.urls")), # optional MTV site
]Step 6: Manual API Test
Start server:
python manage.py runserverList (public):
curl http://127.0.0.1:8000/api/v1/posts/Obtain token:
curl -X POST http://127.0.0.1:8000/api/v1/auth/token/ \
-d "username=alice&password=pass12345"Create post:
curl -X POST http://127.0.0.1:8000/api/v1/posts/ \
-H "Authorization: Token YOUR_TOKEN_HERE" \
-H "Content-Type: application/json" \
-d '{
"title": "API Post",
"slug": "api-post",
"body": "Created via DRF",
"excerpt": "Short",
"published": true
}'Update (author only):
curl -X PATCH http://127.0.0.1:8000/api/v1/posts/1/ \
-H "Authorization: Token YOUR_TOKEN_HERE" \
-H "Content-Type: application/json" \
-d '{"title": "Updated title"}'Search:
curl "http://127.0.0.1:8000/api/v1/posts/?search=django"Step 7: API Tests
blog/tests/test_api_project.py:
python manage.py test blog.tests.test_api_projectStep 8: Optional Frontend Snippet
Vue/React fetch (dev CORS enabled):
const API = "http://127.0.0.1:8000/api/v1";
export async function fetchPosts(token) {
const headers = token ? { Authorization: `Token ${token}` } : {};
const res = await fetch(`${API}/posts/`, { headers });
return res.json();
}Production: same-origin Nginx — API auth chapter.
Step 9: OpenAPI (Optional)
pip install drf-spectacularAdd to INSTALLED_APPS and REST_FRAMEWORK["DEFAULT_SCHEMA_CLASS"] — browse /api/schema/swagger-ui/.
Compare Flask REST project and Spring Boot REST.
Acceptance Checklist
- Anonymous GET list/detail works for published posts
- Unauthenticated POST returns 401/403
- Token login + create/update own post works
- PATCH on another user’s post returns 403
- Pagination and search query params work
- API tests pass in CI — Docker and CI
Common Problems
| Issue | Fix |
|---|---|
| 403 on POST with token | Check Authorization: Token ... header spelling |
| Draft visible in API | Filter published=True in get_queryset for list |
| CORS error from SPA | corsheaders + correct origin |
category_id validation | Ensure category exists in DB |
| Browsable API in prod | Disable HTML renderer in REST_FRAMEWORK |
FAQ## FAQ
JWT instead of Token?
Replace with simplejwt — better for SPAs with refresh tokens.
API versioning v2?
Add path("api/v2/", include(...)) with new serializers.
Rate limiting?
DRF AnonRateThrottle / UserRateThrottle.
GraphQL?
Separate graphene-django stack—not covered here.
Hide unpublished from authors in list?
Custom get_queryset: public sees published; authors see own drafts via query param ?mine=1.