Deploying Django REST Framework on Hop3¶
This guide walks you through deploying a Django REST Framework (DRF) API on Hop3. DRF is the most popular toolkit for building Web APIs with Django.
Prerequisites¶
Before you begin, ensure you have:
- A Hop3 server - Follow the Installation Guide
- The Hop3 CLI - Installed on your local machine
- Python 3.10+ - Install from python.org
- Git - For version control and deployment
Verify your local setup:
Step 1: Create a New DRF Application¶
Install Django and DRF:
Create Django project:
Step 2: Configure the Application¶
Update settings:
import os
from pathlib import Path
BASE_DIR = Path(__file__).resolve().parent.parent
# SECURITY: SECRET_KEY must be set in production
SECRET_KEY = os.environ.get('SECRET_KEY')
DEBUG = os.environ.get('DEBUG', 'False').lower() == 'true'
if not SECRET_KEY and not DEBUG:
raise ValueError("SECRET_KEY environment variable is required in production")
ALLOWED_HOSTS = os.environ.get('ALLOWED_HOSTS', 'localhost,127.0.0.1').split(',')
INSTALLED_APPS = [
'django.contrib.contenttypes',
'django.contrib.auth',
'rest_framework',
'api',
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.middleware.common.CommonMiddleware',
]
ROOT_URLCONF = 'config.urls'
WSGI_APPLICATION = 'config.wsgi.application'
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}
REST_FRAMEWORK = {
'DEFAULT_RENDERER_CLASSES': [
'rest_framework.renderers.JSONRenderer',
],
'DEFAULT_PARSER_CLASSES': [
'rest_framework.parsers.JSONParser',
],
}
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_TZ = True
Create models:
from django.db import models
class Item(models.Model):
name = models.CharField(max_length=255)
price = models.DecimalField(max_digits=10, decimal_places=2)
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.name
Create serializers:
from rest_framework import serializers
from .models import Item
class ItemSerializer(serializers.ModelSerializer):
class Meta:
model = Item
fields = ['id', 'name', 'price', 'created_at']
Create views:
from datetime import datetime
from django.http import HttpResponse
from rest_framework import viewsets, status
from rest_framework.decorators import api_view
from rest_framework.response import Response
from .models import Item
from .serializers import ItemSerializer
def home(request):
return HttpResponse(f"""
<!DOCTYPE html>
<html>
<head>
<title>Welcome to Hop3</title>
<style>
body {{
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
margin: 0;
background: linear-gradient(135deg, #092e20 0%, #44b78b 100%);
color: white;
}}
.container {{ text-align: center; padding: 2rem; }}
h1 {{ font-size: 3rem; margin-bottom: 1rem; }}
p {{ font-size: 1.25rem; opacity: 0.9; }}
a {{ color: white; margin-top: 1rem; display: inline-block; }}
</style>
</head>
<body>
<div class="container">
<h1>Hello from Hop3!</h1>
<p>Your Django REST Framework API is running.</p>
<p>Current time: {datetime.now().isoformat()}</p>
<a href="/api/">API Root</a>
</div>
</body>
</html>
""")
def up(request):
return HttpResponse("OK")
@api_view(['GET'])
def health(request):
return Response({
"status": "ok",
"timestamp": datetime.now().isoformat(),
"version": "1.0.0"
})
@api_view(['GET'])
def info(request):
import django
return Response({
"name": "hop3-tuto-drf",
"version": "1.0.0",
"django_version": django.get_version(),
"framework": "Django REST Framework"
})
class ItemViewSet(viewsets.ModelViewSet):
queryset = Item.objects.all()
serializer_class = ItemSerializer
Configure URLs:
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from api import views
router = DefaultRouter()
router.register(r'items', views.ItemViewSet)
urlpatterns = [
path('', views.home),
path('up', views.up),
path('health', views.health),
path('api/info', views.info),
path('api/', include(router.urls)),
]
Step 3: Initialize Database¶
Step 4: Create Requirements¶
Step 5: Test the Application¶
Test that the application starts correctly (skipped in automated tests - local server tests are flaky):
. venv/bin/activate && python manage.py runserver 0.0.0.0:8000 &
sleep 2
curl -s http://localhost:8000/health
Verify the project structure:
Step 6: Create Deployment Configuration¶
[metadata]
id = "hop3-tuto-drf"
version = "1.0.0"
title = "My DRF API"
[build]
packages = ["python3", "python3-pip"]
[run]
start = "gunicorn config.wsgi --bind 0.0.0.0:$PORT"
before-run = "python manage.py migrate --noinput"
[env]
PYTHONUNBUFFERED = "1"
DJANGO_SETTINGS_MODULE = "config.settings"
[port]
web = 8000
[healthcheck]
path = "/up"
timeout = 30
interval = 60
Deploy to Hop3¶
The following steps require a Hop3 server.
Initialize (First Time Only)¶
Deploy¶
Deploy the application (first deployment creates the app):
Set Environment Variables¶
Set the SECRET_KEY, ALLOWED_HOSTS, and hostname for the application:
Apply Configuration¶
Redeploy to apply the configuration:
Verify Deployment¶
View logs:
hop3 app:logs hop3-tuto-drf
# Your app will be available at:
# http://hop3-tuto-drf.your-hop3-server.example.com
Managing Your Application¶
# Restart the application
hop3 app:restart hop3-tuto-drf
# View/set environment variables
hop3 config:show hop3-tuto-drf
hop3 config:set hop3-tuto-drf NEW_VAR=value
# Scale workers
hop3 ps:scale hop3-tuto-drf web=2
Advanced Configuration¶
Authentication¶
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.TokenAuthentication',
],
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticated',
],
}
Pagination¶
REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE': 10,
}
Filtering¶
# pip install django-filter
REST_FRAMEWORK = {
'DEFAULT_FILTER_BACKENDS': ['django_filters.rest_framework.DjangoFilterBackend']
}
PostgreSQL¶
Example hop3.toml¶
[metadata]
id = "hop3-tuto-drf"
version = "1.0.0"
[build]
before-build = ["python manage.py collectstatic --noinput"]
[run]
start = "gunicorn config.wsgi --bind 0.0.0.0:$PORT --workers 2"
before-run = "python manage.py migrate --noinput"
[port]
web = 8000
[healthcheck]
path = "/up"
[[provider]]
name = "postgres"
plan = "standard"