# DRF SvelteKit

Building a full-stack application using Django REST Framework (DRF) for the backend, SvelteKit for the frontend, Docker for containerization, and GitHub OAuth for authentication. This will provide a basic user management system.

**Project Structure**

We'll start with a high-level structure:

```plaintext
my-app/
├── backend/        # Django REST Framework API
│   ├── core/       # Django core app
│   │   ├── migrations/
│   │   ├── models.py
│   │   ├── serializers.py
│   │   ├── views.py
│   │   ├── admin.py
│   │   └── urls.py
│   ├── users/     # Django users app (where user models & management will reside)
│   │   ├── migrations/
│   │   ├── models.py
│   │   ├── serializers.py
│   │   ├── views.py
│   │   ├── urls.py
│   │   └── signals.py
│   ├── myproject/  # Django project settings
│   │   ├── settings.py
│   │   ├── urls.py
│   │   ├── wsgi.py
│   │   ├── asgi.py
│   │   └── __init__.py
│   ├── dockerfile
│   ├── requirements.txt
│   └── manage.py
├── frontend/      # SvelteKit Frontend
│   ├── src/
│   │   ├── lib/
│   │   ├── routes/
│   │   │   ├── +page.svelte
│   │   │   ├── login/+page.svelte
│   │   │   ├── callback/+page.svelte
│   │   └── app.html
│   ├── static/
│   ├── package.json
│   ├── svelte.config.js
│   ├── vite.config.js
│   └── tsconfig.json
├── docker-compose.yml
├── .env
└── README.md
```

**I. Backend (Django REST Framework)**

1. **Setup Django Project:**
    
    * Create `my-app/backend/` directory
        
    * Create a virtual environment and activate it.
        
    * Install django and DRF: `pip install django djangorestframework django-cors-headers requests python-dotenv`
        
    * Start a django project and a user app:
        
        ```bash
        cd backend
        django-admin startproject myproject .
        python manage.py startapp core
        python manage.py startapp users
        ```
        
2. **Core App (**`backend/core`)
    
    * `backend/core/`[`models.py`](http://models.py):
        
        ```python
        from django.db import models
        # Create your models here.
        ```
        
    * `backend/core/`[`admin.py`](http://admin.py):
        
        ```python
        from django.contrib import admin
        # Register your models here.
        ```
        
    * `backend/core/`[`serializers.py`](http://serializers.py):
        
        ```python
        from rest_framework import serializers
        # Create your serializers here
        ```
        
    * `backend/core/`[`views.py`](http://views.py):
        
        ```python
        from rest_framework.decorators import api_view
        from rest_framework.response import Response
        
        @api_view(['GET'])
        def index(request):
           return Response({"message":"Hello"})
        ```
        
    * `backend/core/`[`urls.py`](http://urls.py):
        
        ```python
        from django.urls import path
        from . import views
        
        urlpatterns = [
            path("", views.index, name="index")
        ]
        ```
        
3. **Users App (**`backend/users`)
    
    * `backend/users/`[`models.py`](http://models.py): Extend the base user model
        
        ```python
        from django.db import models
        from django.contrib.auth.models import AbstractUser
        
        class User(AbstractUser):
            github_id = models.CharField(max_length=255, unique=True, null=True, blank=True)
        
            def __str__(self):
                return self.username
        ```
        
    * `backend/users/`[`serializers.py`](http://serializers.py):
        
        ```python
        from rest_framework import serializers
        from .models import User
        
        class UserSerializer(serializers.ModelSerializer):
            class Meta:
                model = User
                fields = ('id','username','email','github_id')
        ```
        
    * `backend/users/`[`views.py`](http://views.py):
        
        ```python
        import requests
        from django.conf import settings
        from django.shortcuts import redirect
        from django.contrib.auth import authenticate, login
        from rest_framework import status, viewsets
        from rest_framework.decorators import api_view, permission_classes
        from rest_framework.permissions import AllowAny
        from rest_framework.response import Response
        from .models import User
        from .serializers import UserSerializer
        from django.utils import timezone
        
        
        def get_github_access_token(code):
            data = {
                'client_id': settings.GITHUB_CLIENT_ID,
                'client_secret': settings.GITHUB_CLIENT_SECRET,
                'code': code,
            }
            headers = {'Accept': 'application/json'}
            response = requests.post('https://github.com/login/oauth/access_token', data=data, headers=headers)
        
            if response.status_code == 200:
                return response.json().get('access_token')
            return None
        
        def get_github_user_data(access_token):
            headers = {'Authorization': f'Bearer {access_token}'}
            response = requests.get('https://api.github.com/user', headers=headers)
        
            if response.status_code == 200:
                return response.json()
            return None
        
        @api_view(['GET'])
        @permission_classes([AllowAny])
        def github_login(request):
            github_auth_url = 'https://github.com/login/oauth/authorize'
            params = {
                'client_id': settings.GITHUB_CLIENT_ID,
                'redirect_uri': settings.GITHUB_REDIRECT_URI,
                'scope': 'user:email',
            }
            auth_redirect_url = f'{github_auth_url}?{"&".join([f"{key}={value}" for key, value in params.items()])}'
            return redirect(auth_redirect_url)
        
        
        @api_view(['GET'])
        @permission_classes([AllowAny])
        def github_callback(request):
            code = request.GET.get('code')
            if code:
                access_token = get_github_access_token(code)
                if access_token:
                    user_data = get_github_user_data(access_token)
                    if user_data:
                        github_id = user_data['id']
                        username = user_data['login']
                        email = user_data['email']
                        avatar_url = user_data['avatar_url']
        
        
                        try:
                            user = User.objects.get(github_id=github_id)
                        except User.DoesNotExist:
                            user = User.objects.create(username=username, email=email, github_id=github_id, is_active=True, date_joined=timezone.now())
        
                        login(request, user)
                        serializer = UserSerializer(user)
                        return Response(serializer.data, status=status.HTTP_200_OK)
                
            return Response({"message": "Authentication Failed"}, status=status.HTTP_401_UNAUTHORIZED)
        
        class UserViewSet(viewsets.ModelViewSet):
            queryset = User.objects.all()
            serializer_class = UserSerializer
        
            def list(self, request, *args, **kwargs):
                if not request.user.is_authenticated:
                    return Response({"message": "Authentication required"}, status=status.HTTP_401_UNAUTHORIZED)
                queryset = self.filter_queryset(self.get_queryset())
        
                page = self.paginate_queryset(queryset)
                if page is not None:
                    serializer = self.get_serializer(page, many=True)
                    return self.get_paginated_response(serializer.data)
        
                serializer = self.get_serializer(queryset, many=True)
                return Response(serializer.data)
        ```
        
    * `backend/users/`[`signals.py`](http://signals.py):
        
        ```python
         from django.db.models.signals import post_save
         from django.dispatch import receiver
         from django.contrib.auth import get_user_model
         from django.core.management import call_command
        
        
         User = get_user_model()
        
        
        @receiver(post_save, sender=User)
        def create_superuser(sender, instance, created, **kwargs):
            if created:
                if instance.is_superuser:
                    try:
                        call_command('create_superuser', '--username', instance.username, '--email', instance.email, '--noinput')
                    except:
                         pass
        ```
        
    * `backend/users/`[`urls.py`](http://urls.py):
        
        ```python
        from django.urls import path, include
        from rest_framework import routers
        from . import views
        
        router = routers.DefaultRouter()
        router.register(r'users', views.UserViewSet, basename='user')
        
        urlpatterns = [
             path('', include(router.urls)),
             path('github_login/', views.github_login, name='github_login'),
            path('github_callback/', views.github_callback, name='github_callback'),
        ]
        ```
        
4. `backend/myproject/`[`settings.py`](http://settings.py):
    
    * Add `rest_framework`, `corsheaders` and `users` to `INSTALLED_APPS`.
        
    * Configure `CORS_ORIGIN_WHITELIST` with your frontend address (e.g., `'`[`http://localhost:5173`](http://localhost:5173)`'`).
        
    * Add rest framework settings.
        
    * Add `AUTH_USER_MODEL = 'users.User'`
        
    * Add Github Authentication environment variables.
        
        ```python
        INSTALLED_APPS = [
            'django.contrib.admin',
            'django.contrib.auth',
            'django.contrib.contenttypes',
            'django.contrib.sessions',
            'django.contrib.messages',
            'django.contrib.staticfiles',
            'rest_framework',
            'corsheaders',
            'users',
            'core',
        ]
        
        AUTH_USER_MODEL = 'users.User'
        REST_FRAMEWORK = {
            'DEFAULT_AUTHENTICATION_CLASSES': [
                'rest_framework.authentication.SessionAuthentication'
            ],
           'DEFAULT_PERMISSION_CLASSES': [
                'rest_framework.permissions.IsAuthenticated'
            ]
        }
        CORS_ORIGIN_WHITELIST = [
             'http://localhost:5173'
            ]
        
        import os
        from dotenv import load_dotenv
        load_dotenv()
        
        GITHUB_CLIENT_ID = os.getenv("GITHUB_CLIENT_ID")
        GITHUB_CLIENT_SECRET = os.getenv("GITHUB_CLIENT_SECRET")
        GITHUB_REDIRECT_URI = os.getenv("GITHUB_REDIRECT_URI")
        ```
        
5. `backend/myproject/`[`urls.py`](http://urls.py):
    
    ```python
        from django.contrib import admin
        from django.urls import path, include
    
        urlpatterns = [
            path('admin/', admin.site.urls),
            path('api/', include('users.urls')),
            path('api/', include('core.urls')),
        ]
    ```
    
6. `backend/`[`manage.py`](http://manage.py)
    
    ```python
        #!/usr/bin/env python
         import os
         import sys
    
    
         def main():
             """Run administrative tasks."""
             os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings')
             try:
                 from django.core.management import execute_from_command_line
             except ImportError as exc:
                 raise ImportError(
                     "Couldn't import Django. Are you sure it's installed and available on your PYTHONPATH environment variable? Did you forget to activate a virtual environment?"
                 ) from exc
             execute_from_command_line(sys.argv)
    
    
         if __name__ == '__main__':
             main()
    ```
    
7. `backend/requirements.txt`
    
    ```txt
        Django
        djangorestframework
        requests
        django-cors-headers
        python-dotenv
    ```
    
8. `backend/.dockerfile`:
    
    ```dockerfile
    FROM python:3.11-slim-buster
    
    WORKDIR /app
    
    COPY requirements.txt .
    RUN pip install -r requirements.txt
    
    COPY . .
    
    EXPOSE 8000
    CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"]
    ```
    

**II. Frontend (SvelteKit)**

1. **Setup SvelteKit Project:**
    
    * Create `my-app/frontend/` directory
        
    * Install node `npm install -g npm`
        
    * Navigate to the frontend folder `cd frontend/`
        
    * Create svelte project `npm create svelte@latest .`
        
    * Choose SvelteKit, Skeleton project, Typescript, eslint, prettier.
        
    * Install axios: `npm i axios`
        
    * Install `dotenv`: `npm i dotenv --save-dev`
        
2. `frontend/src/routes/+page.svelte`: Main Page
    
    ```svelte
    <script lang="ts">
        import { onMount } from 'svelte';
        import axios from 'axios';
    
    
      let message = "Loading...";
      let users: any[] = [];
    
        const fetchData = async () => {
            try {
                 const response = await axios.get('http://localhost:8000/api/users');
                 users = response.data;
            } catch(error:any){
                  if (error.response && error.response.status === 401) {
                      message = 'You need to login to see this page';
                } else {
                     message = 'Failed to load data.';
                }
             }
    
        };
    
        onMount(() => {
             fetchData();
        });
    </script>
    
    
    <h1>Hello World</h1>
    <p>{message}</p>
    <div>
        {#if users && users.length > 0}
        <h2>Users:</h2>
            {#each users as user}
               <p>User Name: {user.username}</p>
               <p>Email: {user.email}</p>
            {/each}
        {:else}
        <p> No Users </p>
        {/if}
    </div>
     <a href="/login">Login with Github</a>
    ```
    
3. `frontend/src/routes/login/+page.svelte`: Login Page
    
    ```svelte
        <script lang="ts">
           import { goto } from '$app/navigation';
           import { PUBLIC_API_URL } from '$env/static/public';
            import { onMount } from 'svelte';
            onMount(()=>{
                window.location.href = `${PUBLIC_API_URL}/github_login/`;
            })
        </script>
        <div>
            <h1> Login </h1>
            <p> redirecting ...</p>
        </div>
    ```
    
4. `frontend/src/routes/callback/+page.svelte`: Callback Page
    
    ```svelte
      <script lang="ts">
        import { goto } from '$app/navigation';
        import { onMount } from 'svelte';
        import axios from 'axios';
    
        onMount(async () => {
            try {
                 const response = await axios.get(window.location.href);
                   if (response.status === 200) {
                        goto('/');
                    }
            } catch(error:any){
                  goto('/');
            }
         });
     </script>
     <h1> callback </h1>
    <p> redirecting ...</p>
    ```
    
5. `frontend/vite.config.js`:
    
    ```javascript
    import { sveltekit } from '@sveltejs/kit/vite';
    import { defineConfig } from 'vite';
    
    export default defineConfig({
        plugins: [sveltekit()],
      server: {
            port: 5173,
      },
      envPrefix: 'PUBLIC_',
    });
    ```
    
6. `frontend/package.json`:
    
    ```json
     {
      "name": "frontend",
      "version": "0.0.1",
      "private": true,
      "scripts": {
        "dev": "vite dev",
        "build": "vite build",
        "preview": "vite preview",
        "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
        "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch"
      },
      "devDependencies": {
        "@sveltejs/adapter-auto": "^3.0.0",
        "@sveltejs/kit": "^2.0.0",
        "@sveltejs/vite-plugin-svelte": "^3.0.0",
        "axios": "^1.6.7",
        "svelte": "^4.2.7",
        "svelte-check": "^3.6.0",
        "tslib": "^2.4.1",
        "typescript": "^5.0.0",
        "vite": "^5.0.3",
         "dotenv": "^16.4.1"
      },
       "type": "module"
    }
    ```
    
7. `frontend/tsconfig.json`:
    
    ```json
    {
    "extends": "./.svelte-kit/tsconfig.json",
    "compilerOptions": {
        "esModuleInterop": true,
        "allowJs": true,
        "checkJs": true,
        "isolatedModules": true
        }
    }
    ```
    
8. `frontend/.env`:
    
    ```plaintext
       PUBLIC_API_URL=http://localhost:8000/api
    ```
    

**III. Docker and Docker Compose**

1. `docker-compose.yml`:
    
    ```yaml
    version: "3.8"
    
    services:
      backend:
        build: ./backend
        ports:
          - "8000:8000"
        volumes:
          - ./backend:/app
        env_file:
           - ./.env
        restart: always
      frontend:
        build: ./frontend
        ports:
          - "5173:5173"
        volumes:
          - ./frontend:/app
        depends_on:
           - backend
        restart: always
    ```
    
2. `.env`:
    
    ```plaintext
        GITHUB_CLIENT_ID=<YOUR_GITHUB_CLIENT_ID>
        GITHUB_CLIENT_SECRET=<YOUR_GITHUB_CLIENT_SECRET>
        GITHUB_REDIRECT_URI=http://localhost:5173/callback
    ```
    

**Setup and Run**

1. **Create** `.env` file:
    
    * Add your GitHub OAuth app's `client_id`, `client_secret` and the redirect URI to this file (`GITHUB_REDIRECT_URI` should be your frontend callback page. e.g., [`http://localhost:5173/callback`](http://localhost:5173/callback)).
        
    * Go to your [github application](https://github.com/settings/applications) to get this values.
        
2. **Build and Run with Docker Compose:**
    
    ```bash
    docker compose up --build
    ```
    

**Important Notes**

* **Security:** This is a basic setup. In production, you would need proper SSL/TLS, JWT authentication, and secure storage for secrets.
    
* **Error Handling:** Add more robust error handling throughout the application.
    
* **Testing:** Include automated tests for your backend and frontend code.
    
* **Frontend Styling:** You'll want to add CSS/styling to your frontend for a better UX.
    
* **Pagination:** Implement pagination for the user list on the frontend.
    
* **Frontend Authentication State:** Set up more reliable state management to handle user authentication.
    
* **Configuration:** Use environment variables for sensitive information and settings.
    
* **Deployment:** Consider a CI/CD pipeline for deployments.
    

This comprehensive guide should give you a solid foundation for building your full-stack application. Remember to adapt and expand upon these steps to meet your specific project needs. Good luck!
