Back to Articles

Full-Stack Blog Development with React.js and Django: From Setup to Deployment

Posted: 1 year ago·Last Updated: 1 month ago
Blog Cover
Share on LinkedIn
Share on X
Share on Facebook
Share on WhatsApp
Share on Telegram
Share via Email
Copy Link

React.js is a powerful JavaScript library developed by Facebook for building user interfaces. It's known for its declarative approach to building components that manage their own state, making it efficient and easy to develop dynamic web applications. It is commonly used in single-page applications (SPAs) where dynamic content updates are essential, and it's often paired with other libraries and tools for state management (like Redux) and routing (like React Router).

Django is a high-level Python web framework that promotes rapid development and clean, pragmatic design. It follows the "Don't Repeat Yourself" (DRY) principle and emphasizes reusability and pluggability of components. Django is well-suited for building data-driven web applications and is known for its scalability, robustness, and extensive ecosystem of third-party packages (like Django REST Framework for building APIs).

Image

When combined, React.js and Django form a powerful stack for building full-stack web applications. Django serves as the backend, handling data storage, business logic, and API endpoints, while React.js powers the frontend, providing an interactive and responsive user interface. This combination leverages the strengths of both technologies. By leveraging React.js for frontend interactivity and Django for backend robustness, developers can create modern web applications that are efficient, maintainable, and scalable.

Setting Up the Development Environment

Install Python: Ensure Python is installed on your system. You can download it from python.org.

Install Virtualenv (Optional but Recommended)

pip install virtualenv

Create a Virtual Environment: Navigate to your project directory and create a virtual environment

cd your_project_directory
virtualenv venv

Activate the Virtual Environment

source venv/bin/activate

Install Django and Django REST Framework

pip install django
pip install djangorestframework

Create a Django Project

django-admin startproject myblog

Navigate to the Project Directory

cd myblog

Verify the Project Structure

myblog/
├── manage.py
└── myblog/
    ├── __init__.py
    ├── asgi.py
    ├── settings.py
    ├── urls.py
    └── wsgi.py
  • manage.py: Command-line utility for interacting with the Django project.
  • myblog/: The actual Django project directory.
  • __init__.py: An empty file that tells Python this directory should be considered a Python package.
  • asgi.py: ASGI configuration for running your project as an asynchronous application.
  • settings.py: Django project settings (database configuration, static files, etc.).
  • urls.py: URL declarations for the project.
  • wsgi.py: WSGI configuration for running your project as a WSGI application.

With Node.js and npm installed, create a new React.js project

npx create-react-app frontend
cd frontend

Start the Django development server

python manage.py runserver

In another terminal, start the React development server

npm start

Verify Setup

  • Access the Django development server at http://127.0.0.1:8000/ in your web browser. You should see the Django welcome page.
  • Access the React development server at http://localhost:3000/. You should see the React starter page.

Setting up Django models for the blog

Models represent the data structure of your application and define how data will be stored in the database.

If the app doesn’t exist yet, create one using the following command

python manage.py startapp blog

Open the models.py file inside the appropriate app (typically named blog or similar)

from django.db import models
from django.utils import timezone
from django.contrib.auth.models import User
from taggit.managers import TaggableManager
from django.urls import reverse
from django.core.exceptions import ValidationError
from ckeditor_uploader.fields import RichTextUploadingField
from django.utils.deconstruct import deconstructible

class Category(models.TextChoices):
    SALES = 'sales'
    MARKETING = 'marketing'
    BUSINESS = 'business'
    POLITICS = 'politics'
    ART = 'art'

class PublishedManager(models.Manager):
    def get_queryset(self):
        return super(PublishedManager, self).get_queryset().filter(status='published')

@deconstructible
class ImageSizeValidator:
    def __init__(self, max_size_mb=0.5):
        self.max_size_mb = max_size_mb

    def __call__(self, fieldfile_obj):
        filesize = fieldfile_obj.file.size
        megabyte_limit = self.max_size_mb
        if filesize > megabyte_limit * 1024 * 1024:
            raise ValidationError(
                f"File size exceeds the maximum limit of {megabyte_limit}MB."
            )

class Post(models.Model):
    title = models.CharField(max_length=200, help_text="Blog post title")
    slug = models.SlugField(max_length=600, unique_for_date='publish')
    author = models.ForeignKey(
        User, on_delete=models.CASCADE, related_name='blog_post', help_text="Choose author for the post")
    category = models.CharField(
        max_length=50, choices=Category.choices, default=Category.SALES,
        help_text="Category for the post"
    )
    image = models.ImageField(
        upload_to='images/',
        null=True,
        validators=[ImageSizeValidator(max_size_mb=0.5)],
        help_text="Image should be less than 500MB"
    )
    readTime = models.IntegerField(
        null=True, help_text="Read time for the post")
    body = RichTextUploadingField(help_text="Your blog post")
    featured = models.BooleanField(
        default=False, help_text="Check to make the post featured")
    publish = models.DateTimeField(default=timezone.now)
    created = models.DateTimeField(auto_now_add=True)
    updated = models.DateTimeField(auto_now_add=True)
    STATUS_CHOICES = (('draft', 'Draft'), ('published', 'Published'))
    status = models.CharField(
        max_length=20, choices=STATUS_CHOICES, default='draft'
    )
    objects = models.Manager()
    published = PublishedManager()

    # tags = TaggableManager()

    class Meta:
        ordering = ('-publish',)

    def save(self, *args, **kwargs):
        if self.featured:
            try:
                temp = Post.objects.get(featured=True)
                if self != temp:
                    temp.featured = False
                    temp.save()
            except Post.DoesNotExist:
                pass
        super(Post, self).save(*args, **kwargs)

    def __str__(self):
        return self.title

To manage your blog posts via the Django admin interface, register the Post model in blog/admin.py

from django.contrib import admin
from .models import Post

@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
    list_display = ('title', 'author', 'publish', 'status', 'featured', 'category', 'readTime')
    list_filter = ('status', 'category', 'publish', 'author', 'featured')
    search_fields = ('title', 'body')
    prepopulated_fields = {'slug': ('title',)}
    raw_id_fields = ('author',)
    date_hierarchy = 'publish'
    ordering = ('-status', '-publish')

Next, create database migrations for your models and apply the migrations to update your database schema

python manage.py makemigrations
python manage.py migrate

API URLs in your project’s main urls.py file

from django.contrib import admin
from django.urls import path, include, re_path
from django.views.generic import TemplateView
from django.conf import settings
from django.conf.urls.static import static
from rest_framework import routers
from backend import views

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/blog/', include('backend.urls')),
]

if settings.DEBUG:
    urlpatterns += static(settings.MEDIA_URL,
                          document_root=settings.MEDIA_ROOT)

urlpatterns += [path('.*', TemplateView.as_view(template_name='index.html'))]

admin.site.site_header = "Welcome Dennis"
admin.site.site_title = "Dennis Mbugua Dashboard"
admin.site.index_title = "Dennis Mbugua Blog Administration"

Your settings.py file should typically be similar to this:

from pathlib import Path
import mimetypes
import os
import sys
import dj_database_url
from django.core.management.utils import get_random_secret_key

BASE_DIR = Path(__file__).resolve().parent.parent

SECRET_KEY = os.getenv("DJANGO_SECRET_KEY", get_random_secret_key())

DEBUG = os.getenv("DEBUG", "False") == "True"

ALLOWED_HOSTS = os.getenv("DJANGO_ALLOWED_HOSTS", "127.0.0.1,localhost").split(",")

DEVELOPMENT_MODE = os.getenv("DEVELOPMENT_MODE", "False") == "True"

INSTALLED_APPS = [
    'jazzmin',
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'backend',
    'rest_framework',
    'corsheaders',
]

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'whitenoise.middleware.WhiteNoiseMiddleware',
    "corsheaders.middleware.CorsMiddleware",
    "django.middleware.common.CommonMiddleware",
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

ROOT_URLCONF = 'blog.urls'

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

WSGI_APPLICATION = 'blog.wsgi.application'

if DEVELOPMENT_MODE is True:
    DATABASES = {
        "default": {
            "ENGINE": "django.db.backends.sqlite3",
            "NAME": os.path.join(BASE_DIR, "db.sqlite3"),
        }
    }
elif len(sys.argv) > 0 and sys.argv[1] != 'collectstatic':
    if os.getenv("DATABASE_URL", None) is None:
        raise Exception("DATABASE_URL environment variable not defined")
    DATABASES = {
        "default": dj_database_url.parse(os.environ.get("DATABASE_URL")),
    }

STATICFILES_STORAGE = "whitenoise.storage.CompressedStaticFilesStorage"

AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
]

CORS_ORIGIN_WHITELIST = [
    'http://localhost:3000',
]

LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'Africa/Nairobi'
USE_I18N = True
USE_TZ = True

AWS_ACCESS_KEY_ID = ''
AWS_SECRET_ACCESS_KEY = ''
AWS_STORAGE_BUCKET_NAME = ''
AWS_S3_ENDPOINT_URL = ''

AWS_S3_OBJECT_PARAMETERS = {
    'CacheControl': 'max-age=86400',
}
AWS_LOCATION = ''

STATIC_URL = 'https://%s/%s/' % (AWS_S3_ENDPOINT_URL, AWS_LOCATION)
STATICFILES_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage'
DEFAULT_FILE_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage'

static_root = STATIC_ROOT = os.path.join(BASE_DIR, "static")
os.chmod(static_root, 0o755)

DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'

Access API Endpoints

  • List all blog posts: http://127.0.0.1:8000/blog/api/posts/
  • Retrieve, update, or delete a specific post: http://127.0.0.1:8000/blog/api/posts/<post_id>/

Implementing Django REST Framework for API endpoints

Create serializers to convert Django model instances into JSON format for API responses.

from rest_framework import serializers
from .models import Post

class PostSerializer(serializers.ModelSerializer):
    class Meta:
        model = Post
        fields = '__all__'  # or specify individual fields if needed

Create views using Django REST Framework to handle API requests

from django.shortcuts import render, get_object_or_404
from rest_framework.response import Response
from requests import Response
from rest_framework import permissions
from rest_framework.views import APIView
from rest_framework import viewsets
from rest_framework import status
from rest_framework.generics import ListAPIView, RetrieveAPIView
from django.core.mail import send_mail
from django.db.models import Count
from backend.models import Post
from taggit.models import Tag
from .serializers import BlogSerializer
from dataclasses import fields
from rest_framework import serializers
from rest_framework.views import APIView
from django.views.decorators.csrf import csrf_exempt
from django.utils import timezone
from django.db import models

now = timezone.now()  # get the current time

class PostListView(ListAPIView):
    queryset = Post.published.all()
    serializer_class = BlogSerializer

class BlogDetailView(RetrieveAPIView):
    queryset = Post.published.all()
    serializer_class = BlogSerializer
    lookup_field = 'slug'
    permission_classes = (permissions.AllowAny, )

class BlogFeaturedView(ListAPIView):
    queryset = Post.objects.all().filter(featured=True)
    serializer_class = BlogSerializer
    lookup_field = 'slug'
    permission_classes = (permissions.AllowAny, )

class BlogCategoryView(APIView):
    serializer_class = BlogSerializer
    permission_classes = (permissions.AllowAny, )
    http_method_names = ['get', 'head']

    def post(self, request, format=None):
        self.http_method_names.append("GET")
        data = self.request.data
        category = data['id']
        queryset = Post.objects.order_by(
            '-date_created').filter(category__iexact=category)

        serializer = BlogSerializer(queryset, many=True)

        return Response(serializer.data)

Create URLs for your API endpoints

from django.urls import path, include
from . import views
from .views import PostListView, BlogCategoryView, BlogDetailView, BlogFeaturedView
from .feeds import LatestPostsFeed

app_name = 'blog'

urlpatterns = [
    path('', PostListView.as_view(), name='list_view'),
    path('category', BlogCategoryView.as_view(), name='category'),
    path('featured', BlogFeaturedView.as_view(), name='featured'),
    path('<slug>', BlogDetailView.as_view(), name='blog_details'),
    path('feed/', LatestPostsFeed(), name='post_feed'),

]

DRF provides powerful tools for building APIs, including serializers, views, and URL routing.

Setting up a React.js project

Ensure you have Node.js and npm (Node Package Manager) installed on your system.

Navigate to the directory where you want to create your React.js project, then run the following command to create a new project named my-react-blog (replace my-react-blog with your preferred project name), navigate into the newly created project directory and launch the development server.

npx create-react-app my-react-blog
cd my-react-blog
npm start

Once the React project is set up, you’ll see a directory structure similar to this:

my-react-blog/
├── README.md
├── node_modules/
├── package.json
├── public/
│   ├── index.html
│   └── favicon.ico
└── src/
    ├── App.css
    ├── App.js
    ├── App.test.js
    ├── index.css
    ├── index.js
    ├── logo.svg
    └── serviceWorker.js
  • public/: Contains the main index.html file and other static assets.
  • src/: Contains the source code for your React application.
  • index.js: Entry point for the React application.
  • App.js: Main component that renders the application.
  • App.css: Stylesheet for the main component.
  • logo.svg: Example SVG logo file.
  • serviceWorker.js: Optional file for Progressive Web App (PWA) functionality.

Integrating Axios for API Requests

npm install axios

Creating components for the blog

Create a directory named components inside the src directory of your React project

cd my-react-blog/src
mkdir components

Inside the components directory, create individual files for each component. Create a component to display a single blog post.

Inside the components directory, create individual files for each component. Create a component to display a single blog post.

import React, { useState, useEffect, useRef } from "react";
import { Helmet } from "react-helmet";
import { Link, useParams } from "react-router-dom";
import Me from "./me.jpeg";
import { BulletList } from "react-content-loader";
import { ToastContainer, toast } from "react-toastify";
import "react-toastify/dist/ReactToastify.css";
import { useReactToPrint } from "react-to-print";
import {
  TelegramShareButton,
  TelegramIcon,
  TwitterShareButton,
  TwitterIcon,
  EmailShareButton,
  EmailIcon,
  WhatsappIcon,
  WhatsappShareButton,
} from "react-share";
import BlogAPI from "../../API/BlogAPI";
import RecentArticles from "./recentArticles";
import NotFound from "../notFound/NotFound";

export default function BlogDetails() {
  // SCROLL TO TOP
  const scrollToTop = () => {
    window.scrollTo(0, 0);
  };

  const MyBulletListLoader = () => <BulletList />;

  const [error, setError] = useState(false);

  const [blog, setBlog] = useState({});

  const { slug } = useParams();

  const [featuredBlog, setFeaturedBlog] = useState([]);

  const [loading, setLoading] = useState(true);

  const copied = () => toast("👏 Copied to Clipboard!");

  const print = () => toast("Send to print!");

  const [open, setOpen] = useState(false);

  const handleCopyClipBoard = () => {
    setOpen(true);
    navigator.clipboard.writeText(window.location.toString());
  };

  const componentRef = useRef();
  const handlePrint = useReactToPrint({
    content: () => componentRef.current,
  });

  useEffect(() => {
    // Loader
    setLoading(true);
    setTimeout(() => {
      setLoading(false);
    }, 1000);

    // FeaturedAticle
    const fetchData = async () => {
      try {
        const res = await BlogAPI.get(`/api/blog/featured`);
        // If there are no featured posts
        if (res.data && res.data.length > 0) {
          setFeaturedBlog(res.data[0]);
        }
      } catch (err) {}
    };
    fetchData();

    // BlogPost
    const fetchBlog = async () => {
      try {
        const response = await BlogAPI.get(`/api/blog/${slug}`); // <-- passed to API URL
        setBlog(response.data);
      } catch (err) {
        if (err.response && err.response.status === 404) {
          setError(true);
        } else {
          // Handle other errors, such as network issues
          // console.error(err);
        }
      }
    };
    fetchBlog();
  }, [slug]);

  const createBlog = () => {
    return { __html: blog.body };
  };
  const capitalLetter = (word) => {
    if (word) return word.charAt(0).toUpperCase() + word.slice(1);
    return "";
  };

  const shareUrl = `https://www.yourwebsite.com/${slug}`;

  let getTime = (blog) => {
    const options = { year: "numeric", month: "long", day: "numeric" };
    return new Date(blog).toLocaleDateString("en", options);
  };

  return (
    <>
      {error ? (
        <NotFound />
      ) : (
        <div className="container mt-5">
          <div className="row">
            <div className="col-lg-8 mt-5 mb-5">
              <nav aria-label="breadcrumb">
                <ol className="breadcrumb bg-white">
                  <li className="breadcrumb-item fw-bold">
                    <Link to="/articles">
                      <i className="fa fa-angle-double-left" /> Back to blog
                    </Link>
                  </li>
                </ol>
              </nav>
              {loading ? (
                <MyBulletListLoader />
              ) : (
                <article>


                  <section
                    ref={componentRef}
                    removeAfterPrint={true}
                    documentTitle="www.dennismbugua.co.ke"
                  >
                    <header className="mb-4">
                      <h1 className="fw-bolder mb-1">
                        {capitalLetter(blog.title)}
                      </h1>
                    </header>
                    <p className="post-meta">
                      <i className="fa fa-calendar-days"></i>{" "}
                      {getTime(blog.updated)}
                    </p>
                    <figure className="mb-4">
                      <img
                        className="img-fluid rounded w-100"
                        src={blog.image}
                        alt={blog.title}
                        style={{
                          height: "600px",
                          width: "500px",
                          borderRadius: "10px",
                        }}
                      />
                    </figure>

                    <p
                      className="fs-5 text-dark"
                      dangerouslySetInnerHTML={createBlog()}
                    />
                  </section>

                  <section className="text-center border-top border-bottom py-4 mb-4">
                    <p>
                      <strong>
                        Share this post with your friends and spread the
                        knowledge!
                      </strong>
                    </p>
                    <ul className="mr-3">
                      <li>
                        <TwitterShareButton
                          url={shareUrl}
                          title={blog.title}
                          via={"dennismbugua_"}
                        >
                          <TwitterIcon size={40} round={true} />
                        </TwitterShareButton>
                      </li>

                      <li>
                        <EmailShareButton
                          subject="IMPORTANT AND URGENT!"
                          url={shareUrl}
                          body="This is one of the most awesome article I've read today"
                        >
                          <EmailIcon size={40} round={true} />
                        </EmailShareButton>
                      </li>

                      <li>
                        <WhatsappShareButton
                          url={shareUrl}
                          title={blog.title}
                          separator={" "}
                        >
                          <WhatsappIcon size={40} round={true} />
                        </WhatsappShareButton>
                      </li>

                      <li>
                        <TelegramShareButton url={shareUrl} title={blog.title}>
                          <TelegramIcon size={40} round={true} />
                        </TelegramShareButton>
                      </li>

                      <li>
                        <i
                          className="fa wp-icon fa-solid fa-print fa-1x cursor-pointer"
                          onClick={() => {
                            handlePrint();
                            print();
                          }}
                        ></i>
                      </li>
                      <li>
                        <i
                          className="wp-icon fa fa-copy fa-solid fa-1x"
                          onClick={() => {
                            handleCopyClipBoard();
                            copied();
                          }}
                        ></i>
                      </li>
                      <ToastContainer />
                    </ul>
                  </section>

                </article>
              )}
            </div>

            <div className="col-lg-4">
              <div className="position-sticky" style={{ top: "6rem" }}>
                {/* FeaturedPost */}
                <div className="card">
                  <div
                    style={{
                      borderRadius: "10px",
                      padding: "10px",
                      border: "2px",
                    }}
                  >
                    <div className="card-header text-uppercase fw-bold text-center">
                      Featured Article
                    </div>
                    <div className="card-body" style={{ borderRadius: "10px" }}>
                      <p>
                        {loading ? (
                          <MyBulletListLoader />
                        ) : featuredBlog.length === 0 ? (
                          <p>Stay tuned for my upcoming featured articles!</p>
                        ) : (
                          <Link
                            to={`/articles/${featuredBlog.slug}`}
                            onClick={scrollToTop}
                            className="text-decoration-none fw-bold fs-5"
                          >
                            {capitalLetter(featuredBlog.title)}
                          </Link>
                        )}
                      </p>
                      <hr className="text-dark" />
                    </div>
                  </div>
                </div>

                {/* Recent Articles */}
                <div className="card mt-5">
                  <div className="card-header text-uppercase fw-bold text-center">
                    Recent Article
                  </div>
                  <RecentArticles />
                </div>
              </div>
            </div>
          </div>
        </div>
      )}
    </>
  );
}

Create a component to display a list of blog posts

import React, { useEffect, useState } from "react";
import { Link } from "react-router-dom";
import { BulletList } from "react-content-loader";
import BlogAPI from "../../API/BlogAPI";

export default function BlogFeeds() {
  // SCROLL TO TOP
  const scrollToTop = () => {
    window.scrollTo(0, 0);
  };

  const MyBulletListLoader = () => <BulletList />;

  const [blogs, setBlogs] = useState([]);
  const [filter, setFilter] = useState("");
  const [loading, setLoading] = useState(true);

  const searchText = (event) => {
    setFilter(event.target.value);
  };

  const dataSearch = blogs.filter((blogPost) => {
    return Object.keys(blogPost).some((key) =>
      blogPost[key]
        .toString()
        .toLowerCase()
        .includes(filter.toString().toLowerCase())
    );
  });

  const capitalLetter = (word) => {
    return word ? `${word.charAt(0).toUpperCase()}${word.slice(1)}` : "";
  };

  useEffect(() => {
    // Loader
    setLoading(true);
    setTimeout(() => {
      setLoading(false);
    }, 2500);

    const fetchBlogs = async () => {
      try {
        const res = await BlogAPI.get(`/api/blog`);
        setBlogs(res.data);
      } catch (err) {
        // Handle error
      }
    };
    fetchBlogs();
  }, []);

  const getBlogs = () => {
    return dataSearch.map((blogPost, i) => (
      <React.Fragment key={i}>
        <div className="col">
          <div className="post-preview border-bottom">
            {loading ? (
              <MyBulletListLoader />
            ) : (
              <Link
                to={`/articles/${blogPost.slug}`}
                onClick={scrollToTop}
                className="text-decoration-none"
              >
                <h3 className="post-title">{capitalLetter(blogPost.title)}</h3>
                <h4 className="post-subtitle">
                  {capitalLetter(blogPost.subtitle)}
                </h4>
                <p>
                  <i className="fa-solid fa-clock"></i> {blogPost.readTime} Min
                  Read
                </p>
              </Link>
            )}
          </div>
        </div>
      </React.Fragment>
    ));
  };

  return (
    <>
      <div className="container" style={{ marginTop: "100px" }}>
        <div className="row height d-flex justify-content-center align-items-center">
          <div className="col-md-8">
            <div className="search">
              <i className="fa fa-search"></i>
              <input
                type="text"
                className="form-control"
                value={filter}
                onChange={searchText.bind(this)}
                placeholder="Search an article"
              />
            </div>
          </div>
        </div>
      </div>

      <div className="container px-4 px-lg-5 mb-5">
        <div className="row gx-4 gx-lg-5 justify-content-center">
          <div className="col-md-10 col-lg-8 col-xl-9 ">{getBlogs()}</div>
        </div>
      </div>
    </>
  );
}

Create a component to display latest blog posts

import React, { useEffect, useState } from "react";
import { Link } from "react-router-dom";
import { BulletList } from "react-content-loader";
import BlogAPI from "../../API/BlogAPI";

export default function RecentArticles() {
  // SCROLL TO TOP
  const scrollToTop = () => {
    window.scrollTo(0, 0);
  };

  const MyBulletListLoader = () => <BulletList />;

  const [blogs, setBlogs] = useState([]);
  const [loading, setLoading] = useState(true);

  const capitalLetter = (word) => {
    return word ? `${word.charAt(0).toUpperCase()}${word.slice(1)}` : "";
  };

  useEffect(() => {
    // Loader
    setLoading(true);
    setTimeout(() => {
      setLoading(false);
    }, 2000);

    const fetchBlogs = async () => {
      try {
        const res = await BlogAPI.get(`/api/blog`);
        setBlogs(res.data);
      } catch (err) {
        // Handle error
      }
    };
    fetchBlogs();
  }, []);

  const getBlogs = () => {
    return blogs.slice(0, 2).map((blogPost, index) => (
      <div className="card-body" key={index} style={{ borderRadius: "10px" }}>
        {loading ? (
          <MyBulletListLoader />
        ) : (
          <Link
            to={`/articles/${blogPost.slug}`}
            onClick={scrollToTop}
            className="text-decoration-none fw-bold fs-5"
          >
            {capitalLetter(blogPost.title)}
          </Link>
        )}
        <hr className="text-dark" />
      </div>
    ));
  };

  return <div>{getBlogs()}</div>;
}

Create a component to display featured blog posts

import React, { useState, useEffect } from "react";
import { Link, useParams } from "react-router-dom";
import { BulletList } from "react-content-loader";
import BlogAPI from "../../API/BlogAPI";

export default function FeaturedArticles() {
  const scrollToTop = () => {
    window.scrollTo(0, 0);
  };

  const MyBulletListLoader = () => <BulletList />;
  const { slug } = useParams();

  const [featuredBlog, setFeaturedBlog] = useState([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    setLoading(true);

    const fetchData = async () => {
      try {
        const res = await BlogAPI.get("/api/blog/featured");
        if (res.data && res.data.length > 0) {
          setFeaturedBlog(res.data[0]);
        }
        setLoading(false);
      } catch (err) {
        console.log(err);
        setFeaturedBlog([]);
        setLoading(false);
      }
    };

    fetchData();
  }, []);

  return (
    <>
      <div className="bg-image p-5 text-center shadow-1-strong rounded mb-5 text-white">
        {loading ? (
          <h1 className="mb-3 h2">
            <MyBulletListLoader />
          </h1>
        ) : featuredBlog.length === 0 ? (
          <h1 className="mb-3 h2">Stay tuned for my upcoming featured articles!</h1>
        ) : (
          <>
            <h1 className="mb-3 h2">
              <Link to={`/articles/${featuredBlog.slug}`} onClick={scrollToTop} className="text-decoration-underline text-white fw-bold text-capitalize display-3">
                {featuredBlog.title}
              </Link>
            </h1>
            <button type="button" className="btn btn-primary">
              <h6 className="fw-bold">
                <Link to={`/articles/${featuredBlog.slug}`} className="text-white fs-3">
                  Explore Now
                </Link>
              </h6>
            </button>
          </>
        )}
      </div>
    </>
  );
}

Use these components in your App.js or any other parent component to render blog-related content

import React from "react";
import { Routes, Route, useNavigate } from "react-router-dom";
import BlogFeeds from "./components/blog/BlogFeeds";
import { lazy, Suspense } from "react";
import { ErrorBoundary } from "react-error-boundary";
import Home from "./components/homePage/Home";
import ErrorFallback from "./components/error/ErrorFallback";

const BlogDetails = lazy(() => import("./components/blog/BlogDetails"));

function App() {
  const navigate = useNavigate();

  return (
    <>
      {/* <NavBar /> */}
      <Routes>
        <Route
          exact
          path="/articles/:slug"
          element={
            <ErrorBoundary
              onReset={() => navigate("/")}
              fallbackRender={({ error, resetErrorBoundary }) => (
                <ErrorFallback
                  error={error}
                  resetErrorBoundary={resetErrorBoundary}
                />
              )}
            >
              <Suspense>
                <>
                  <BlogDetails />
                </>
              </Suspense>
            </ErrorBoundary>
          }
        />

        <Route
          exact
          path="/articles"
          element={
            <ErrorBoundary
              onReset={() => navigate("/")}
              fallbackRender={({ error, resetErrorBoundary }) => (
                <ErrorFallback
                  error={error}
                  resetErrorBoundary={resetErrorBoundary}
                />
              )}
            >
              <Suspense>
                <BlogFeeds />
              </Suspense>
            </ErrorBoundary>
          }
        />

        <Route
          exact
          path="/"
          element={
            <ErrorBoundary
              onReset={() => navigate("/")}
              fallbackRender={({ error, resetErrorBoundary }) => (
                <ErrorFallback
                  error={error}
                  resetErrorBoundary={resetErrorBoundary}
                />
              )}
            >
              <Suspense>
                <>
                  <Home />
                </>
              </Suspense>
            </ErrorBoundary>
          }
        />
      </Routes>
    </>
  );
}

export default App;

Pushing the Site to GitHub

Get your site in a git repository and then push that repository to GitHub. Initialize your Django project as a git repository

git init

Exclude certain files added that are unnecessary for deployment by adding that directory to Git’s ignore list

nano .gitignore

Add files to your repositories

git add .

Make your initial commit

git commit -m "Initial Blog App"

Push your local files to GitHub

git remote add origin https://github.com/your_username/django-app

Rename the default branch main, to match what GitHub expect

git branch -M main

Push your main branch to GitHub’s main branch

git push -u origin main

Your files will transfer. Enter your GitHub credentials when prompted to push your code.

[secondary_label Output]
Enumerating objects: 12, done.
Counting objects: 100% (12/12), done.
Delta compression using up to 8 threads
Compressing objects: 100% (9/9), done.
Writing objects: 100% (12/12), 3.98 KiB | 150.00 KiB/s, done.
Total 12 (delta 1), reused 0 (delta 0)
remote: Resolving deltas: 100% (1/1), done.
To github.com:yourUsername/django-app.git
 * [new branch]      main -> main
Branch 'main' set up to track remote branch 'main' from 'origin'.

Deploying the Django backend

  • Sign Up Free on Digital Ocean and get a $200 credit
  • Click Create App
  • Connect your GitHub account and allow DigitalOcean to access your repositories.
  • Click Install and Authorize
  • Under the Repository section, select the your repository from the dropdown menu. Select your branch name and source directory.
  • Click Edit to the right of the Run Command section. In this example, your completed run command should be gunicorn — worker-tmp-dir /dev/shm django_app.wsgi
Image
  • Click Save to confirm the change, then click Back.
  • For this deployment, select the development database and name it db. Then, click the Create and Attach button.
Image
  • Click Next to proceed.
  • Once the build process completes, the interface will show you a healthy site. Access your app’s console through the Console tab and perform the Django first launch tasks by running the following commands:
python manage.py migrate
python manage.py createsuperuser
  • Click on the link to your app provided by App Platform. This link should take you to the standard initial Django page.

Hurray! And now you have a Django app deployed to App Platform. Any changes that you make and push to GitHub will be automatically deployed.

Deploying the React.js frontend

Involves preparing your project for production, building optimized assets, and deploying those assets to a web server or a cloud platform.

There are several options for hosting including Netlify, Vercel, AWS S3, GitHub Pages, Firebase Hosting, or Surge.sh are ideal for hosting static websites. You can also deploy frontend alongside backend on platforms like Digital Ocean, AWS Elastic Beanstalk, Google Cloud Platform, or Azure App Service.

Get a custom domain, configure DNS settings to point to your hosting provider

Additional Tips:

  • Environment Variables: Use environment variables to manage configuration between environments (e.g., development, production).
  • Continuous Deployment: Set up automatic deployments triggered by Git commits for seamless updates.

Additional features to explore

Email Newsletters

Integrate email newsletter functionality using ReactJS with ConvertKit (popular for content creators) or even Django’s email capabilities or third-party services like Mailchimp or SendGrid to enable users to subscribe and receive updates.

User Authentication & Authorization

Implement user authentication and authorization using Django’s built-in authentication system or third-party libraries like Django Allauth or OAuth providers.

Real-time Communication

Add real-time features using WebSockets with Django Channels or integrate with third-party services like Pusher or Firebase for live updates and messaging.

Data Visualization

Utilize libraries like Chart.js or D3.js for interactive data visualization, presenting insights and trends from your blog content.

Testing & Continuous Integration

Set up automated testing using Django’s test framework and frontend testing with tools like Jest or Cypress. Implement continuous integration and deployment (CI/CD) pipelines with GitHub Actions or GitLab CI.

Progressive Web App (PWA) Features

Enable PWA features like offline support, push notifications, and background sync using service workers to enhance the user experience.

Localization & Internationalization

Support multiple languages and locales using Django’s internationalization features and react-i18next for the frontend, making your blog accessible to a global audience.

Analytics & Monitoring

Integrate analytics tools like Google Analytics or Matomo for tracking user behavior and engagement. Set up application performance monitoring (APM) with tools like New Relic or Datadog to ensure optimal performance.

Security Enhancements

Implement additional security measures such as HTTPS, Content Security Policy (CSP), and cross-site scripting (XSS) protection to safeguard your blog from security threats and vulnerabilities.

Get full access the source code of the project

Happy Coding!

Share on LinkedIn
Share on X
Share on Facebook
Share on WhatsApp
Share on Telegram
Share via Email
Copy Link

Meet Dennis, a seasoned software engineer with 10 years of experience transforming ideas into digital reality. He has successfully guided countless projects from concept to deployment, bringing innovative solutions to life. With a passion for crafting exceptional software, Dennis has helped countless clients achieve their goals.

Click here to learn more

Popular Posts

No popular posts available.

Recommended For You

No related posts found.