DEV Community

Cover image for Building a Blog API with Django REST Framework
Clement Odok
Clement Odok

Posted on

Building a Blog API with Django REST Framework

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
Enter fullscreen mode Exit fullscreen mode

Install Django, DRF, and CORS headers:

pip install django djangorestframework django-cors-headers
Enter fullscreen mode Exit fullscreen mode

Start a new Django project:

django-admin startproject  django_project
Enter fullscreen mode Exit fullscreen mode

Create the apps:

python manage.py startapp accounts
python manage.py startapp posts
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Update django_project/settings.py to use the custom user model:

AUTH_USER_MODEL = "accounts.CustomUser"
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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'
        )
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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"),
]
Enter fullscreen mode Exit fullscreen mode

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")),
]
Enter fullscreen mode Exit fullscreen mode

Run the API

Apply migrations and create a superuser:

python manage.py makemigrations
python manage.py migrate
python manage.py createsuperuser
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
  }'
Enter fullscreen mode Exit fullscreen mode

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')
Enter fullscreen mode Exit fullscreen mode

Run your tests with:

python manage.py test
Enter fullscreen mode Exit fullscreen mode

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)