Building a Blog API with Django REST Framework (DRF) and a Custom User Model
APIs power most of today's applications. In this tutorial, we'll walk through building a Blog API using Django REST Framework (DRF) with a Custom User Model.
We'll create two apps:
- accounts → for authentication and managing users
- posts → for creating, listing, and managing blog posts
Project Setup
First, create and activate a virtual environment:
mkdir blog_api && cd blog_api
python -m venv venv
source venv/bin/activate # Linux/macOS
Install Django, DRF, and CORS headers:
pip install django djangorestframework django-cors-headers
Start a new Django project:
django-admin startproject django_project
Create the apps:
python manage.py startapp accounts
python manage.py startapp posts
Add them in django_project/settings.py
:
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
"rest_framework",
"corsheaders",
"accounts",
"posts",
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'corsheaders.middleware.CorsMiddleware',#new
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
# CORS settings
CORS_ALLOWED_ORIGINS = [
"http://localhost:3000", # React default
"http://127.0.0.1:3000",
"http://localhost:8080", # Vue default
"http://127.0.0.1:8080",
]
# For development only - allows all origins
# CORS_ALLOW_ALL_ORIGINS = True
Custom User Model (accounts app)
Inside accounts/models.py
:
from django.db import models
from django.contrib.auth.models import AbstractUser
# Create your models here.
class CustomUser(AbstractUser):
name = models.CharField(max_length=100, blank=True, null=True)
def __str__(self):
return self.username
Update django_project/settings.py
to use the custom user model:
AUTH_USER_MODEL = "accounts.CustomUser"
This tells Django to use our CustomUser model instead of the default User model for authentication and user management throughout the project.
Admin Configuration
Update accounts/admin.py
to use the custom forms:
Custom User Forms
Create accounts/forms.py
for Django admin integration:
from django.contrib.auth.forms import UserCreationForm, UserChangeForm
from .models import CustomUser
class CustomUserCreationForm(UserCreationForm):
class Meta(UserCreationForm.Meta):
model = CustomUser
fields = UserCreationForm.Meta.fields + ('name',)
class CustomUserChangeForm(UserChangeForm):
class Meta:
model = CustomUser
fields = UserChangeForm.Meta.fields
Admin Configuration
Update accounts/admin.py
to use the custom forms:
from django.contrib import admin
from .models import CustomUser
from .forms import CustomUserCreationForm, CustomUserChangeForm
from django.contrib.auth.admin import UserAdmin
# Register your models here.
class CustomUserAdmin(UserAdmin):
add_form = CustomUserCreationForm
form = CustomUserChangeForm
model = CustomUser
list_display = ['username', 'email', 'name', 'is_staff']
fieldsets = UserAdmin.fieldsets + (
(None, {'fields': ('name',)}),
)
add_fieldsets = UserAdmin.add_fieldsets + (
(None, {'fields': ('name',)}),
)
search_fields = ('email', 'username', 'name')
# Register the admin class with the model
admin.site.register(CustomUser, CustomUserAdmin)
Blog Posts (posts app)
In posts/models.py
:
from django.db import models
from django.conf import settings
# Create your models here.
class Post(models.Model):
title = models.CharField(max_length=50)
content = models.TextField()
author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return self.title
Serializer & Views
In posts/serializers.py
:
from rest_framework import serializers
from .models import Post
class PostSerializer(serializers.ModelSerializer):
class Meta:
model = Post
fields = (
'id',
'title',
'content',
'author',
'created_at',
'updated_at'
)
In posts/views.py
:
from django.shortcuts import render
from rest_framework import generics
from .models import Post
from .serializers import PostSerializer
# Create your views here.
class PostList(generics.ListCreateAPIView):
queryset = Post.objects.all()
serializer_class = PostSerializer
class PostDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Post.objects.all()
serializer_class = PostSerializer
In posts/urls.py
:
from django.urls import path
from .views import PostList, PostDetail
urlpatterns = [
path("posts/", PostList.as_view(), name="post-list"),
path("posts/<int:pk>/", PostDetail.as_view(), name="post-detail"),
]
In django_project/urls.py
:
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path("admin/", admin.site.urls),
path("api/", include("posts.urls")),
]
Run the API
Apply migrations and create a superuser:
python manage.py makemigrations
python manage.py migrate
python manage.py createsuperuser
Follow the prompts to create an admin user - you'll be asked for:
- Username
- Email address (optional)
- Name (optional - our custom field)
- Password
Then run the server:
python manage.py runserver
You can now:
- Access the API at
http://127.0.0.1:8000/api/
- Login to Django Admin at
http://127.0.0.1:8000/admin/
with your superuser credentials - Manage users and posts through the admin interface
Now test the endpoints:
-
GET /api/posts/
→ list posts -
POST /api/posts/
→ create a new post -
GET /api/posts/<id>/
→ retrieve a specific post -
PUT /api/posts/<id>/
→ update a post -
DELETE /api/posts/<id>/
→ delete a post
Testing Your API
You can test your API using tools like:
- Postman - GUI interface for API testing
- curl - Command line tool
Example using curl to create a post:
curl -X POST http://127.0.0.1:8000/api/posts/ \
-H "Content-Type: application/json" \
-d '{
"title": "My First Blog Post",
"content": "This is the content of my first blog post!",
"author": 1
}'
Writing Unit Tests
Create comprehensive tests in posts/tests.py
:
from django.test import TestCase
from .models import Post
from django.contrib.auth import get_user_model
# Create your tests here.
class BlogTests(TestCase):
@classmethod
def setUpTestData(cls):
cls.user = get_user_model().objects.create_user(
username='testuser',
email='test@gmail.com',
password='secret'
)
cls.post = Post.objects.create(
title='A good title',
content='Nice content',
author=cls.user,
)
def test_post_model(self):
self.assertEqual(self.post.title, 'A good title')
self.assertEqual(self.post.content, 'Nice content')
self.assertEqual(self.post.author.username, 'testuser')
self.assertEqual(str(self.post), 'A good title')
Run your tests with:
python manage.py test
Next Steps
From here, you could extend the API with:
- Authentication (JWT/Session-based)
- Permissions (who can create/edit posts)
- deployment
- API documentation with DRF's browsable API or Swagger
- Frontend integration - Now ready for React, Vue, or Angular apps!
Conclusion
You now have a fully functional Blog API with:
- A Custom User Model for flexibility
- Full CRUD operations for blog posts
- Clean, maintainable code structure
This foundation provides a solid starting point for building more complex blog applications or learning advanced Django REST Framework concepts.
What features would you add to this Blog API? Share your thoughts in the comments below!
Happy coding!
Top comments (0)