Skip to content

khoiddinh/react-django-boilerplate

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

7 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

My App

Note: Only use npm for package handling in this repo (DO NOT USE YARN).

Table of Contents


Global Setup

IMPORTANT: This must be done by ONE person BEFORE any team members start their local setup.

If you are just setting up your local machine as a team member, skip to Local Setup.

Replace myapp with your actual app name in the following files:

1. docker-compose.yml

Update the following lines:

# Line 7: Change container_name
container_name: <appname>_postgres

# Line 12: Change POSTGRES_DB
POSTGRES_DB: <appname>_db

Example: If your app name is myproject, change:

  • myapp_postgresmyproject_postgres
  • myapp_dbmyproject_db

2. .env file (or .env.example if it exists)

If you have a .env.example file in the backend directory, update the DATABASE_URL:

DATABASE_URL=postgresql://postgres:postgres@localhost:5432/<appname>_db

Example: If your app name is myproject, change:

  • myapp_dbmyproject_db

Make sure the database name matches what you set in docker-compose.yml.

3. Commit these changes

After making these changes, commit and push them to the repository so all team members use the same app name.


Prerequisites

  • Node.js (for frontend development)

    • Version: Node.js 20.19.0 or higher, or Node.js 22.12.0 or higher
    • Required by Vite and React dependencies (specified in package.json engines field)
    • Check your version: node --version
    • If using nvm, run nvm use in the frontend directory (uses .nvmrc file)
    • Download from nodejs.org if needed
  • Docker Desktop (for local PostgreSQL database)

    • Download from docker.com
    • Ensure Docker is running before starting the database

Local Setup

Database Setup with Docker

This project uses PostgreSQL only (no SQLite fallback). We use Docker to run PostgreSQL locally, so you don't need to install PostgreSQL on your machine.

1. Start PostgreSQL with Docker

From the project root directory, start the PostgreSQL container:

docker-compose up -d

This will:

  • Download the PostgreSQL 15 image (if not already downloaded)
  • Start a PostgreSQL container named <appname>_postgres
  • Create a database named <appname>_db
  • Expose PostgreSQL on port 5432

Default credentials:

  • Username: postgres
  • Password: postgres
  • Database: <appname>_db
  • Port: 5432

2. Verify Database is Running

Check that the container is running:

docker-compose ps

You should see the postgres service running. You can also check the logs:

docker-compose logs postgres

3. Configure DATABASE_URL

If you have a .env.example file, copy it to .env:

cp backend/.env.example backend/.env

The .env file should contain:

DATABASE_URL=postgresql://postgres:postgres@localhost:5432/<appname>_db

Next Step: After configuring the database, proceed to Backend Setup to set up your backend environment and run migrations.

Docker Commands Reference

# Start the database
docker-compose up -d

# Stop the database
docker-compose down

# Stop and remove volumes (deletes all data)
docker-compose down -v

# View database logs
docker-compose logs -f postgres

# Check database status
docker-compose ps

# Access PostgreSQL CLI
docker-compose exec postgres psql -U postgres -d <appname>_db

Heroku PostgreSQL

When deploying to Heroku, the DATABASE_URL is automatically set by Heroku. See the Heroku Deployment section below.

Backend Setup

1. Create and Activate Virtual Environment

First, cd into the backend directory and create a virtual environment:

cd backend
python3 -m venv venv
source venv/bin/activate

2. Install Dependencies

Install the required packages:

pip install -r requirements.txt

3. Configure VS Code

Create a .vscode directory in the outermost directory (where backend and frontend directories are located).

Create a settings.json file with the following contents:

{
  "python.defaultInterpreterPath": "backend/venv/bin/python3",
  "python.analysis.extraPaths": ["backend"]
}

Then:

  1. Open VS Code search (CMD+Shift+P / Ctrl+Shift+P)
  2. Type "python interpreter" and press Enter
  3. Select "Use Python from python.defaultInterpreterPath setting"
  4. If it's not there, search "Developer: reload window" and refresh a couple of times
  5. Once you've selected the Python interpreter, refresh again until the import errors go away

4. Configure Environment Variables

Create a .env file in the /backend directory using .env.example as a template:

cp .env.example .env

The .env file should already contain the correct DATABASE_URL for Docker. No changes needed unless you want to customize the database credentials.

Note: Make sure you've completed the Database Setup with Docker section before proceeding with migrations.

5. Run Database Migrations

After setting up your .env file and starting the Docker database, create and run the initial migrations:

cd backend
source venv/bin/activate  # if not already activated
python manage.py makemigrations core
python manage.py migrate

This will create the necessary database tables for your Django application.

Frontend Setup

1. Verify Node.js Version

Before installing dependencies, ensure you have the correct Node.js version:

node --version

You need Node.js 20.19.0+ or 22.12.0+.

If using nvm (Node Version Manager), you can automatically use the correct version:

cd frontend
nvm use

The required version is specified in package.json (engines field) and .nvmrc. If you see warnings about unsupported engine versions, update Node.js.

2. Install Dependencies

cd into the frontend directory and install dependencies:

cd frontend
npm ci

Important: Use npm ci (NOT npm install). If you are modifying package-lock.json, you are doing it wrong.

3. Run the Application

To run both the frontend and backend in a local development environment:

cd frontend
npm run start:full

To just run the backend:

npm run dev

A full list of commands can be found in package.json under the "scripts" section.


AWS Configuration

This project supports AWS S3 for file storage and AWS SES (Simple Email Service) for sending emails. Both services use the same AWS credentials.

Shared AWS Credentials

  1. Create IAM User with S3 and SES Permissions:

    • Go to IAM Console
    • Create a new user with programmatic access
    • Attach policies for both AmazonS3FullAccess and AmazonSESFullAccess (or create custom policies with minimal permissions)
    • Save the Access Key ID and Secret Access Key
  2. Configure Shared Credentials in .env:

    AWS_ACCESS_KEY_ID=your-access-key-id
    AWS_SECRET_ACCESS_KEY=your-secret-access-key

AWS S3 Setup (Optional - for file storage)

  1. Create an S3 Bucket:

    • Go to AWS S3 Console
    • Create a new bucket
    • Note the bucket name and region
  2. Configure in .env:

    AWS_STORAGE_BUCKET_NAME=your-bucket-name
    AWS_S3_REGION_NAME=us-east-1

AWS SES Setup (Optional - for sending emails)

  1. Verify Your Email Domain or Email Address:

    • Go to AWS SES Console
    • If you're in the SES sandbox, verify your email address
    • For production, verify your domain to send from any email address on that domain
  2. Request Production Access (if needed):

    • In SES sandbox, you can only send to verified email addresses
    • Request production access to send to any email address
  3. Configure in .env:

    AWS_SES_REGION_NAME=us-east-1
    AWS_SES_REGION_ENDPOINT=email.us-east-1.amazonaws.com
    SES_FROM_EMAIL=[email protected]

    Note:

    • SES uses the same AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY as S3
    • SES_FROM_EMAIL must be a verified email address or domain in AWS SES
    • If AWS credentials or SES_FROM_EMAIL are not provided, the application will use the console email backend (emails printed to console) for development
  4. Test Email Sending:

    from django.core.mail import send_mail
    
    send_mail(
        'Subject',
        'Message body',
        '[email protected]',
        ['[email protected]'],
        fail_silently=False,
    )

Heroku Deployment

Prerequisites

  1. Install the Heroku CLI
  2. Create a Heroku account at heroku.com
  3. Login to Heroku:
    heroku login

Initial Setup

  1. Create a Heroku App:

    heroku create your-app-name
  2. Add PostgreSQL Add-on:

    heroku addons:create heroku-postgresql:mini

    The mini plan is free for development. For production, consider upgrading to a paid plan.

    This automatically sets the DATABASE_URL environment variable.

  3. Set Environment Variables:

    heroku config:set SECRET_KEY=your-secret-key-here
    heroku config:set ENVIRONMENT=production
    heroku config:set ALLOWED_HOSTS=your-app-name.herokuapp.com

    Set any other environment variables from your .env file as needed:

    # AWS Credentials (shared for S3 and SES)
    heroku config:set AWS_ACCESS_KEY_ID=your-key
    heroku config:set AWS_SECRET_ACCESS_KEY=your-secret
    
    # AWS S3 (for file storage)
    heroku config:set AWS_STORAGE_BUCKET_NAME=your-bucket
    heroku config:set AWS_S3_REGION_NAME=us-east-1
    
    # AWS SES (for sending emails)
    heroku config:set AWS_SES_REGION_NAME=us-east-1
    heroku config:set AWS_SES_REGION_ENDPOINT=email.us-east-1.amazonaws.com
    heroku config:set [email protected]
  4. Deploy Backend:

    cd backend
    git subtree push --prefix backend heroku main

    Or if using a separate Heroku git remote:

    git remote add heroku https://git.heroku.com/your-app-name.git
    git subtree push --prefix backend heroku main
  5. Run Migrations:

    heroku run python manage.py migrate
  6. Create Superuser (optional):

    heroku run python manage.py createsuperuser

Frontend Deployment

For the frontend, you can:

  1. Deploy to Heroku (with a buildpack):

    heroku create your-frontend-app-name
    heroku buildpacks:set heroku/nodejs
    cd frontend
    git subtree push --prefix frontend heroku main
  2. Or deploy to Vercel/Netlify:

    • Connect your GitHub repository
    • Set the build directory to frontend
    • Configure build command: npm ci && npm run build
    • Set publish directory to frontend/dist

Useful Heroku Commands

# View logs
heroku logs --tail

# Open app in browser
heroku open

# Run Django shell
heroku run python manage.py shell

# Check database connection
heroku pg:info

# View all config vars
heroku config

# Restart dynos
heroku restart

Other Tips

Making Routes

  1. Build routes in core/views.py
  2. Add them to config/urls.py

Example:

# core/views.py
from django.http import JsonResponse

def my_view(request):
    return JsonResponse({"message": "Hello, World!"})
# config/urls.py
from django.urls import path
from core.views import my_view

urlpatterns = [
    path('api/my-endpoint/', my_view, name='my_view'),
]

Creating Django Models

Models define your database structure. Create models in core/models.py.

Basic Model Structure

from django.db import models

class MyModel(models.Model):
    # Field definitions here
    
    class Meta:
        ordering = ['-created_at']  # Optional: default ordering
    
    def __str__(self):
        return self.name  # String representation for admin/Django shell

Common Field Types

class Example(models.Model):
    # Text fields
    name = models.CharField(max_length=100)  # Short text, required
    description = models.TextField()  # Long text, required
    bio = models.TextField(null=True, blank=True)  # Optional long text
    
    # Numbers
    age = models.IntegerField()  # Integer, required
    price = models.DecimalField(max_digits=10, decimal_places=2)  # Decimal
    score = models.IntegerField(default=0)  # With default value
    
    # Boolean
    is_active = models.BooleanField(default=True)
    is_published = models.BooleanField(default=False)
    
    # Dates
    created_at = models.DateTimeField(auto_now_add=True)  # Set on creation
    updated_at = models.DateTimeField(auto_now=True)  # Updated on save
    
    # Foreign keys (see Relationships section below)
    # Many-to-many (see Relationships section below)

null=True vs blank=True

These are two different concepts that work together:

  • null=True: Allows the database column to store NULL values

    • Database-level constraint
    • Use for fields that might not have a value
  • blank=True: Allows the field to be empty in forms/admin

    • Validation-level constraint
    • Use for fields that are optional in forms

Common combinations:

# Required field (default)
name = models.CharField(max_length=100)
# Database: NOT NULL, Forms: Required

# Optional field (can be empty in DB and forms)
description = models.TextField(null=True, blank=True)
# Database: NULL allowed, Forms: Optional

# Field with default (recommended for optional fields)
score = models.IntegerField(default=0)
# Database: NOT NULL (defaults to 0), Forms: Optional (shows 0)

# Date that's auto-set (doesn't need null/blank)
created_at = models.DateTimeField(auto_now_add=True)
# Database: NOT NULL, Forms: Hidden/auto-set

When to use defaults:

  • Use default= when: You want a sensible fallback value

    score = models.IntegerField(default=0)  # Better than null=True
    is_active = models.BooleanField(default=True)
  • Use null=True, blank=True when: The field is truly optional and has no sensible default

    middle_name = models.CharField(max_length=50, null=True, blank=True)
    bio = models.TextField(null=True, blank=True)
  • Use null=True, blank=True, default=None when: You want to explicitly track "not set" vs "empty string"

    # For CharField/TextField, empty string '' vs None can be different
    optional_text = models.CharField(max_length=100, null=True, blank=True, default=None)

Model Relationships

1. ForeignKey (Many-to-One)

A ForeignKey creates a many-to-one relationship. One model "belongs to" another.

from django.conf import settings

class Module(models.Model):
    course = models.ForeignKey(
        Course, 
        on_delete=models.CASCADE,  # Delete module if course is deleted
        related_name='modules'  # Access via course.modules.all()
    )
    name = models.CharField(max_length=100)

on_delete options:

  • CASCADE: Delete this object when parent is deleted (default for most cases)
  • PROTECT: Prevent deletion of parent if children exist
  • SET_NULL: Set to NULL when parent is deleted (requires null=True)
  • SET_DEFAULT: Set to default value when parent is deleted
  • DO_NOTHING: Don't do anything (not recommended)

Usage:

# Create
module = Module.objects.create(course=my_course, name="Intro")

# Access parent
module.course  # Returns Course object

# Access children (via related_name)
course.modules.all()  # All modules for this course
2. ManyToManyField (Many-to-Many)

Creates a many-to-many relationship. Both models can have multiple of the other.

from django.conf import settings

class Course(models.Model):
    name = models.CharField(max_length=100)
    
    # Many-to-many with User model
    students = models.ManyToManyField(
        settings.AUTH_USER_MODEL,  # Use settings.AUTH_USER_MODEL, not 'User'
        related_name='courses_as_student',
        blank=True  # Can have no students initially
    )
    
    teachers = models.ManyToManyField(
        settings.AUTH_USER_MODEL,
        related_name='courses_as_teacher',
        blank=True
    )

Important: Always use settings.AUTH_USER_MODEL (string) or import get_user_model() for the User model, not 'User' directly.

Usage:

# Add relationships
course.students.add(user1, user2)
course.teachers.add(teacher1)

# Remove relationships
course.students.remove(user1)

# Clear all
course.students.clear()

# Check if user is in course
if user in course.students.all():
    pass

# Access from User side (via related_name)
user.courses_as_student.all()  # All courses where user is a student
user.courses_as_teacher.all()  # All courses where user is a teacher

Many-to-Many Through Table (Custom Intermediate Model):

If you need to store extra data about the relationship, use through:

class Course(models.Model):
    name = models.CharField(max_length=100)
    students = models.ManyToManyField(
        settings.AUTH_USER_MODEL,
        through='Enrollment',  # Custom intermediate model
        related_name='enrolled_courses'
    )

class Enrollment(models.Model):
    course = models.ForeignKey(Course, on_delete=models.CASCADE)
    student = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    enrolled_at = models.DateTimeField(auto_now_add=True)
    grade = models.IntegerField(null=True, blank=True)
    
    class Meta:
        unique_together = [['course', 'student']]  # Prevent duplicate enrollments

Usage with through:

# Create enrollment with extra data
Enrollment.objects.create(course=course, student=user, grade=95)

# Access still works
course.students.all()  # All students
user.enrolled_courses.all()  # All courses

# Access intermediate model
enrollment = Enrollment.objects.get(course=course, student=user)
enrollment.grade  # Access extra data
3. OneToOneField (One-to-One)

Creates a one-to-one relationship. Each instance of one model relates to exactly one instance of another.

class UserProfile(models.Model):
    user = models.OneToOneField(
        settings.AUTH_USER_MODEL,
        on_delete=models.CASCADE,
        related_name='profile'
    )
    bio = models.TextField(blank=True)
    avatar = models.ImageField(upload_to='avatars/', null=True, blank=True)

Usage:

# Create
profile = UserProfile.objects.create(user=user, bio="Hello")

# Access
user.profile  # Returns UserProfile (or raises DoesNotExist)
user.profile.bio

# Check if exists
if hasattr(user, 'profile'):
    pass

Complete Example

from django.db import models
from django.conf import settings
from django.contrib.auth import get_user_model

User = get_user_model()  # Or use settings.AUTH_USER_MODEL as string

class Course(models.Model):
    name = models.CharField(max_length=100)
    description = models.TextField(blank=True)
    created_at = models.DateTimeField(auto_now_add=True)
    
    # Many-to-many relationships
    students = models.ManyToManyField(
        User,
        related_name='student_courses',
        blank=True
    )
    teachers = models.ManyToManyField(
        User,
        related_name='teacher_courses',
        blank=True
    )
    
    def __str__(self):
        return self.name

class Module(models.Model):
    course = models.ForeignKey(
        Course,
        on_delete=models.CASCADE,
        related_name='modules'
    )
    title = models.CharField(max_length=200)
    order = models.IntegerField(default=0)
    is_published = models.BooleanField(default=False)
    
    class Meta:
        ordering = ['order']
    
    def __str__(self):
        return f"{self.course.name} - {self.title}"

class Grade(models.Model):
    student = models.ForeignKey(User, on_delete=models.CASCADE)
    module = models.ForeignKey(Module, on_delete=models.CASCADE)
    score = models.IntegerField(default=0)
    total = models.IntegerField(default=100)
    graded_at = models.DateTimeField(auto_now=True)
    
    class Meta:
        unique_together = [['student', 'module']]  # One grade per student per module
    
    def __str__(self):
        return f"{self.student.username} - {self.module.title}: {self.score}/{self.total}"

After Creating/Modifying Models

  1. Create migrations:

    python manage.py makemigrations core
  2. Review migrations (optional):

    python manage.py sqlmigrate core 0001
  3. Apply migrations:

    python manage.py migrate
  4. Register in admin (optional):

    # core/admin.py
    from django.contrib import admin
    from .models import Course, Module, Grade
    
    admin.site.register(Course)
    admin.site.register(Module)
    admin.site.register(Grade)

Troubleshooting

Database Connection Issues

  • Docker not running: Ensure Docker Desktop is running before starting the database
  • Container not started: Run docker-compose up -d from the project root
  • Port conflict: If port 5432 is already in use, stop other PostgreSQL instances or change the port in docker-compose.yml
  • Verify DATABASE_URL: Check that DATABASE_URL in .env matches your database name (e.g., postgresql://postgres:postgres@localhost:5432/<appname>_db)
  • Check container status: Run docker-compose ps to see if the container is running
  • View logs: Run docker-compose logs postgres to see database logs

Import Errors in VS Code

  • Reload the window: CMD+Shift+P → "Developer: Reload Window"
  • Verify Python interpreter is set correctly
  • Check that python.analysis.extraPaths includes "backend" in .vscode/settings.json

Node.js Version Issues

  • "Unsupported engine" warnings: Update Node.js to version 20.19.0+ or 22.12.0+
    • Check current version: node --version
    • Download the latest LTS version from nodejs.org
    • If using nvm, run: nvm install 20.19.0 or nvm install 22.12.0
    • After updating, delete node_modules and package-lock.json, then run npm ci again

License

[Add your license here]

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published