├── README.md ├── backend ├── .gitignore ├── Dockerfile ├── manage.py ├── mysaas │ ├── __init__.py │ ├── asgi.py │ ├── settings.py │ ├── urls.py │ └── wsgi.py ├── requirements.txt └── users │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── migrations │ ├── 0001_initial.py │ └── __init__.py │ └── models.py ├── docker-compose.yml ├── frontend ├── .eslintrc.json ├── .gitignore ├── Dockerfile ├── README.md ├── next.config.mjs ├── package-lock.json ├── package.json ├── postcss.config.mjs ├── public │ ├── next.svg │ └── vercel.svg ├── src │ └── app │ │ ├── favicon.ico │ │ ├── globals.css │ │ ├── layout.tsx │ │ └── page.tsx ├── tailwind.config.ts └── tsconfig.json ├── nginx └── nginx.conf └── postgres └── init.sql /README.md: -------------------------------------------------------------------------------- 1 | # next-django-fastapi-fullstack-tutorial 2 | > This tutorial demonstrates a full stack application using Django, FastAPI and Next.js suitable for hosting in Docker-type containers. 3 | 4 | ## Pre-requisite 5 | 6 | Ensure you have a working Docker and docker-compose locally. 7 | 8 | ## Running 9 | 10 | To run the stack locally just launch via docker-compose. 11 | 12 | ```sh 13 | docker-compose up -d 14 | ``` 15 | 16 | ## Learn more 17 | 18 | You can read the [full step by step explanation and tutorial](https://damianhodgkiss.com/tutorials/fullstack-django-fastapi-nextjs/) if you wish to re-create it for learning purposes. 19 | -------------------------------------------------------------------------------- /backend/.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # poetry 98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 99 | # This is especially recommended for binary packages to ensure reproducibility, and is more 100 | # commonly ignored for libraries. 101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 102 | #poetry.lock 103 | 104 | # pdm 105 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 106 | #pdm.lock 107 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 108 | # in version control. 109 | # https://pdm.fming.dev/latest/usage/project/#working-with-version-control 110 | .pdm.toml 111 | .pdm-python 112 | .pdm-build/ 113 | 114 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 115 | __pypackages__/ 116 | 117 | # Celery stuff 118 | celerybeat-schedule 119 | celerybeat.pid 120 | 121 | # SageMath parsed files 122 | *.sage.py 123 | 124 | # Environments 125 | .env 126 | .venv 127 | env/ 128 | venv/ 129 | ENV/ 130 | env.bak/ 131 | venv.bak/ 132 | 133 | # Spyder project settings 134 | .spyderproject 135 | .spyproject 136 | 137 | # Rope project settings 138 | .ropeproject 139 | 140 | # mkdocs documentation 141 | /site 142 | 143 | # mypy 144 | .mypy_cache/ 145 | .dmypy.json 146 | dmypy.json 147 | 148 | # Pyre type checker 149 | .pyre/ 150 | 151 | # pytype static type analyzer 152 | .pytype/ 153 | 154 | # Cython debug symbols 155 | cython_debug/ 156 | 157 | # PyCharm 158 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 159 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 160 | # and can be added to the global gitignore or merged into this file. For a more nuclear 161 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 162 | #.idea/ -------------------------------------------------------------------------------- /backend/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.11.5-bullseye 2 | 3 | WORKDIR /app 4 | 5 | ENV PYTHONDONTWRITEBYTECODE 1 6 | ENV PYTHONUNBUFFERED 1 7 | 8 | RUN pip install --upgrade pip 9 | 10 | COPY . /app/ 11 | 12 | RUN mkdir -p /app/staticfiles/ 13 | 14 | RUN pip install -r requirements.txt 15 | 16 | EXPOSE 8000 17 | 18 | CMD ["uvicorn", "mysaas.asgi:application", "--host", "0.0.0.0"] 19 | -------------------------------------------------------------------------------- /backend/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Django's command-line utility for administrative tasks.""" 3 | import os 4 | import sys 5 | 6 | 7 | def main(): 8 | """Run administrative tasks.""" 9 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mysaas.settings") 10 | try: 11 | from django.core.management import execute_from_command_line 12 | except ImportError as exc: 13 | raise ImportError( 14 | "Couldn't import Django. Are you sure it's installed and " 15 | "available on your PYTHONPATH environment variable? Did you " 16 | "forget to activate a virtual environment?" 17 | ) from exc 18 | execute_from_command_line(sys.argv) 19 | 20 | 21 | if __name__ == "__main__": 22 | main() 23 | -------------------------------------------------------------------------------- /backend/mysaas/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damianhodgkiss/next-django-fastapi-fullstack-tutorial/40332f3c995e7a1215c5e7e98b7c460d02d8cc37/backend/mysaas/__init__.py -------------------------------------------------------------------------------- /backend/mysaas/asgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | ASGI config for mysaas project. 3 | 4 | It exposes the ASGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/5.0/howto/deployment/asgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.asgi import get_asgi_application 13 | from fastapi import FastAPI 14 | 15 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mysaas.settings") 16 | 17 | application = get_asgi_application() 18 | fastapp = FastAPI( 19 | servers=[ 20 | { 21 | "url": "/api/v1", 22 | "description": "V1", 23 | } 24 | ] 25 | ) 26 | 27 | 28 | def init(app: FastAPI): 29 | @app.get("/health") 30 | def health_check(): 31 | return {'status': 'ok'} 32 | 33 | 34 | init(fastapp) 35 | -------------------------------------------------------------------------------- /backend/mysaas/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for mysaas project. 3 | 4 | Generated by 'django-admin startproject' using Django 5.0.1. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/5.0/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/5.0/ref/settings/ 11 | """ 12 | 13 | import os 14 | from pathlib import Path 15 | 16 | # Build paths inside the project like this: BASE_DIR / 'subdir'. 17 | BASE_DIR = Path(__file__).resolve().parent.parent 18 | 19 | 20 | # Quick-start development settings - unsuitable for production 21 | # See https://docs.djangoproject.com/en/5.0/howto/deployment/checklist/ 22 | 23 | # SECURITY WARNING: keep the secret key used in production secret! 24 | SECRET_KEY = "django-insecure-rxts$d4^xi5us3dc21bi8o_25if^vd7=idqjnfn(kf+s)behcl" 25 | 26 | # SECURITY WARNING: don't run with debug turned on in production! 27 | DEBUG = True 28 | 29 | ALLOWED_HOSTS = ['localhost'] 30 | 31 | 32 | # Application definition 33 | 34 | INSTALLED_APPS = [ 35 | "users.apps.UsersConfig", 36 | "django_use_email_as_username.apps.DjangoUseEmailAsUsernameConfig", 37 | "django.contrib.admin", 38 | "django.contrib.auth", 39 | "django.contrib.contenttypes", 40 | "django.contrib.sessions", 41 | "django.contrib.messages", 42 | "django.contrib.staticfiles", 43 | ] 44 | 45 | MIDDLEWARE = [ 46 | "django.middleware.security.SecurityMiddleware", 47 | "django.contrib.sessions.middleware.SessionMiddleware", 48 | "django.middleware.common.CommonMiddleware", 49 | "django.middleware.csrf.CsrfViewMiddleware", 50 | "django.contrib.auth.middleware.AuthenticationMiddleware", 51 | "django.contrib.messages.middleware.MessageMiddleware", 52 | "django.middleware.clickjacking.XFrameOptionsMiddleware", 53 | ] 54 | 55 | ROOT_URLCONF = "mysaas.urls" 56 | 57 | TEMPLATES = [ 58 | { 59 | "BACKEND": "django.template.backends.django.DjangoTemplates", 60 | "DIRS": [], 61 | "APP_DIRS": True, 62 | "OPTIONS": { 63 | "context_processors": [ 64 | "django.template.context_processors.debug", 65 | "django.template.context_processors.request", 66 | "django.contrib.auth.context_processors.auth", 67 | "django.contrib.messages.context_processors.messages", 68 | ], 69 | }, 70 | }, 71 | ] 72 | 73 | WSGI_APPLICATION = "mysaas.wsgi.application" 74 | 75 | 76 | # Database 77 | # https://docs.djangoproject.com/en/5.0/ref/settings/#databases 78 | 79 | DATABASES = { 80 | "default": { 81 | "ENGINE": "django.db.backends.postgresql", 82 | "NAME": os.getenv("POSTGRES_DB", default="mysaas"), 83 | "USER": os.getenv("POSTGRES_USER", default="mysaas"), 84 | "PASSWORD": os.getenv("POSTGRES_PASSWORD", default="mysaas"), 85 | "HOST": os.getenv("POSTGRES_HOST", default="postgres"), 86 | "PORT": os.getenv("POSTGRES_PORT", default="5432"), 87 | } 88 | } 89 | 90 | # Password validation 91 | # https://docs.djangoproject.com/en/5.0/ref/settings/#auth-password-validators 92 | 93 | AUTH_PASSWORD_VALIDATORS = [ 94 | { 95 | "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", 96 | }, 97 | { 98 | "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", 99 | }, 100 | { 101 | "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", 102 | }, 103 | { 104 | "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", 105 | }, 106 | ] 107 | 108 | 109 | # Internationalization 110 | # https://docs.djangoproject.com/en/5.0/topics/i18n/ 111 | 112 | LANGUAGE_CODE = "en-us" 113 | 114 | TIME_ZONE = "UTC" 115 | 116 | USE_I18N = True 117 | 118 | USE_TZ = True 119 | 120 | 121 | # Static files (CSS, JavaScript, Images) 122 | # https://docs.djangoproject.com/en/5.0/howto/static-files/ 123 | 124 | STATIC_URL = "static/" 125 | 126 | # Default primary key field type 127 | # https://docs.djangoproject.com/en/5.0/ref/settings/#default-auto-field 128 | 129 | DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" 130 | 131 | AUTH_USER_MODEL = 'users.User' 132 | -------------------------------------------------------------------------------- /backend/mysaas/urls.py: -------------------------------------------------------------------------------- 1 | """ 2 | URL configuration for mysaas project. 3 | 4 | The `urlpatterns` list routes URLs to views. For more information please see: 5 | https://docs.djangoproject.com/en/5.0/topics/http/urls/ 6 | Examples: 7 | Function views 8 | 1. Add an import: from my_app import views 9 | 2. Add a URL to urlpatterns: path('', views.home, name='home') 10 | Class-based views 11 | 1. Add an import: from other_app.views import Home 12 | 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') 13 | Including another URLconf 14 | 1. Import the include() function: from django.urls import include, path 15 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 16 | """ 17 | 18 | from django.contrib import admin 19 | from django.urls import path 20 | 21 | urlpatterns = [ 22 | path("admin/", admin.site.urls), 23 | ] 24 | -------------------------------------------------------------------------------- /backend/mysaas/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for mysaas project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/5.0/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mysaas.settings") 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /backend/requirements.txt: -------------------------------------------------------------------------------- 1 | Django==5.0.3 2 | uvicorn==0.25.0 3 | fastapi==0.109.1 4 | django-use-email-as-username==1.4.0 5 | psycopg2==2.9.9 6 | -------------------------------------------------------------------------------- /backend/users/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damianhodgkiss/next-django-fastapi-fullstack-tutorial/40332f3c995e7a1215c5e7e98b7c460d02d8cc37/backend/users/__init__.py -------------------------------------------------------------------------------- /backend/users/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from django_use_email_as_username.admin import BaseUserAdmin 3 | 4 | from .models import User 5 | 6 | admin.site.register(User, BaseUserAdmin) 7 | -------------------------------------------------------------------------------- /backend/users/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class UsersConfig(AppConfig): 5 | name = 'users' 6 | verbose_name = 'Custom User Management' 7 | -------------------------------------------------------------------------------- /backend/users/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.0.3 on 2024-06-26 08:45 2 | 3 | import django.utils.timezone 4 | import django_use_email_as_username.models 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | initial = True 11 | 12 | dependencies = [ 13 | ('auth', '0012_alter_user_first_name_max_length'), 14 | ] 15 | 16 | operations = [ 17 | migrations.CreateModel( 18 | name='User', 19 | fields=[ 20 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 21 | ('password', models.CharField(max_length=128, verbose_name='password')), 22 | ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), 23 | ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), 24 | ('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')), 25 | ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')), 26 | ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')), 27 | ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')), 28 | ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), 29 | ('email', models.EmailField(max_length=254, unique=True, verbose_name='email address')), 30 | ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')), 31 | ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')), 32 | ], 33 | options={ 34 | 'abstract': False, 35 | }, 36 | managers=[ 37 | ('objects', django_use_email_as_username.models.BaseUserManager()), 38 | ], 39 | ), 40 | ] 41 | -------------------------------------------------------------------------------- /backend/users/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damianhodgkiss/next-django-fastapi-fullstack-tutorial/40332f3c995e7a1215c5e7e98b7c460d02d8cc37/backend/users/migrations/__init__.py -------------------------------------------------------------------------------- /backend/users/models.py: -------------------------------------------------------------------------------- 1 | from django_use_email_as_username.models import BaseUser, BaseUserManager 2 | 3 | 4 | class User(BaseUser): 5 | objects = BaseUserManager() 6 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.1' 2 | 3 | volumes: 4 | postgres-data: 5 | 6 | services: 7 | postgres: 8 | image: pgautoupgrade/pgautoupgrade:latest 9 | restart: always 10 | volumes: 11 | - postgres-data:/var/lib/postgresql/data 12 | - ./postgres/init.sql:/docker-entrypoint-initdb.d/init.sql 13 | environment: 14 | POSTGRES_PASSWORD: postgres 15 | 16 | admin: 17 | build: 18 | context: backend 19 | dockerfile: Dockerfile 20 | image: backend:latest 21 | ports: 22 | - '8000:8000' 23 | volumes: 24 | - ./backend:/app 25 | command: uvicorn mysaas.asgi:application --host 0.0.0.0 --reload 26 | depends_on: 27 | - postgres 28 | 29 | api: 30 | build: 31 | context: backend 32 | dockerfile: Dockerfile 33 | image: backend:latest 34 | ports: 35 | - '8001:8000' 36 | volumes: 37 | - ./backend:/app 38 | command: uvicorn mysaas.asgi:fastapp --host 0.0.0.0 --reload 39 | depends_on: 40 | - postgres 41 | 42 | frontend: 43 | build: 44 | context: frontend 45 | image: frontend:latest 46 | ports: 47 | - '3000:3000' 48 | volumes: 49 | - ./frontend:/app 50 | command: yarn dev 51 | depends_on: 52 | - api 53 | 54 | nginx: 55 | image: nginx 56 | ports: 57 | - "80:80" 58 | volumes: 59 | - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro 60 | depends_on: 61 | - frontend 62 | - admin 63 | - api 64 | -------------------------------------------------------------------------------- /frontend/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | -------------------------------------------------------------------------------- /frontend/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:21-bookworm AS base 2 | ARG DEBIAN_FRONTEND=noninteractive 3 | 4 | ENV PORT 3000 5 | ENV HOSTNAME "0.0.0.0" 6 | # ENV NEXT_TELEMETRY_DISABLED 1 7 | 8 | USER root 9 | RUN apt-get update && apt-get install -y --no-install-recommends \ 10 | libc6-dev \ 11 | libvips-dev \ 12 | build-essential \ 13 | && rm -rf /var/lib/apt/lists/* 14 | 15 | WORKDIR /app 16 | RUN chown node:node /app 17 | 18 | 19 | ## Install dependencies based on the preferred package manager, and build the app 20 | FROM base AS builder 21 | USER root 22 | 23 | # COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./ 24 | COPY --chown=node:node . . 25 | USER node 26 | RUN \ 27 | if [ -f yarn.lock ]; then yarn config set global-folder /app/.yarn && yarn --frozen-lockfile; \ 28 | elif [ -f package-lock.json ]; then npm ci; \ 29 | elif [ -f pnpm-lock.yaml ]; then yarn global add pnpm && pnpm i --frozen-lockfile; \ 30 | else echo "Lockfile not found." && exit 1; \ 31 | fi 32 | 33 | RUN yarn build 34 | 35 | ## Copy the built app to a new image 36 | FROM base AS runner 37 | COPY --from=builder --chown=node:node /app/public ./public 38 | COPY --from=builder --chown=node:node /app/.next/standalone ./ 39 | COPY --from=builder --chown=node:node /app/.next/static ./.next/static 40 | COPY --from=builder --chown=node:node /app/node_modules/ ./node_modules/ 41 | 42 | USER node 43 | ENV PATH /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/app/node_modules/.bin 44 | EXPOSE 3000 45 | CMD ["node", "server.js"] -------------------------------------------------------------------------------- /frontend/README.md: -------------------------------------------------------------------------------- 1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). 2 | 3 | ## Getting Started 4 | 5 | First, run the development server: 6 | 7 | ```bash 8 | npm run dev 9 | # or 10 | yarn dev 11 | # or 12 | pnpm dev 13 | # or 14 | bun dev 15 | ``` 16 | 17 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 18 | 19 | You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. 20 | 21 | This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font. 22 | 23 | ## Learn More 24 | 25 | To learn more about Next.js, take a look at the following resources: 26 | 27 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 28 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 29 | 30 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! 31 | 32 | ## Deploy on Vercel 33 | 34 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. 35 | 36 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. 37 | -------------------------------------------------------------------------------- /frontend/next.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | output: 'standalone', 4 | }; 5 | 6 | export default nextConfig; 7 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "react": "^18", 13 | "react-dom": "^18", 14 | "next": "14.2.4" 15 | }, 16 | "devDependencies": { 17 | "typescript": "^5", 18 | "@types/node": "^20", 19 | "@types/react": "^18", 20 | "@types/react-dom": "^18", 21 | "postcss": "^8", 22 | "tailwindcss": "^3.4.1", 23 | "eslint": "^8", 24 | "eslint-config-next": "14.2.4" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /frontend/postcss.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('postcss-load-config').Config} */ 2 | const config = { 3 | plugins: { 4 | tailwindcss: {}, 5 | }, 6 | }; 7 | 8 | export default config; 9 | -------------------------------------------------------------------------------- /frontend/public/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damianhodgkiss/next-django-fastapi-fullstack-tutorial/40332f3c995e7a1215c5e7e98b7c460d02d8cc37/frontend/src/app/favicon.ico -------------------------------------------------------------------------------- /frontend/src/app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | :root { 6 | --foreground-rgb: 0, 0, 0; 7 | --background-start-rgb: 214, 219, 220; 8 | --background-end-rgb: 255, 255, 255; 9 | } 10 | 11 | @media (prefers-color-scheme: dark) { 12 | :root { 13 | --foreground-rgb: 255, 255, 255; 14 | --background-start-rgb: 0, 0, 0; 15 | --background-end-rgb: 0, 0, 0; 16 | } 17 | } 18 | 19 | body { 20 | color: rgb(var(--foreground-rgb)); 21 | background: linear-gradient( 22 | to bottom, 23 | transparent, 24 | rgb(var(--background-end-rgb)) 25 | ) 26 | rgb(var(--background-start-rgb)); 27 | } 28 | 29 | @layer utilities { 30 | .text-balance { 31 | text-wrap: balance; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /frontend/src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from "next"; 2 | import { Inter } from "next/font/google"; 3 | import "./globals.css"; 4 | 5 | const inter = Inter({ subsets: ["latin"] }); 6 | 7 | export const metadata: Metadata = { 8 | title: "Create Next App", 9 | description: "Generated by create next app", 10 | }; 11 | 12 | export default function RootLayout({ 13 | children, 14 | }: Readonly<{ 15 | children: React.ReactNode; 16 | }>) { 17 | return ( 18 | 19 | {children} 20 | 21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /frontend/src/app/page.tsx: -------------------------------------------------------------------------------- 1 | import Image from "next/image"; 2 | 3 | export default function Home() { 4 | return ( 5 |
6 |
7 |

8 | Get started by editing  9 | src/app/page.tsx 10 |

11 |
12 | 18 | By{" "} 19 | Vercel Logo 27 | 28 |
29 |
30 | 31 |
32 | Next.js Logo 40 |
41 | 42 |
43 | 49 |

50 | Docs{" "} 51 | 52 | -> 53 | 54 |

55 |

56 | Find in-depth information about Next.js features and API. 57 |

58 |
59 | 60 | 66 |

67 | Learn{" "} 68 | 69 | -> 70 | 71 |

72 |

73 | Learn about Next.js in an interactive course with quizzes! 74 |

75 |
76 | 77 | 83 |

84 | Templates{" "} 85 | 86 | -> 87 | 88 |

89 |

90 | Explore starter templates for Next.js. 91 |

92 |
93 | 94 | 100 |

101 | Deploy{" "} 102 | 103 | -> 104 | 105 |

106 |

107 | Instantly deploy your Next.js site to a shareable URL with Vercel. 108 |

109 |
110 |
111 |
112 | ); 113 | } 114 | -------------------------------------------------------------------------------- /frontend/tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from "tailwindcss"; 2 | 3 | const config: Config = { 4 | content: [ 5 | "./src/pages/**/*.{js,ts,jsx,tsx,mdx}", 6 | "./src/components/**/*.{js,ts,jsx,tsx,mdx}", 7 | "./src/app/**/*.{js,ts,jsx,tsx,mdx}", 8 | ], 9 | theme: { 10 | extend: { 11 | backgroundImage: { 12 | "gradient-radial": "radial-gradient(var(--tw-gradient-stops))", 13 | "gradient-conic": 14 | "conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))", 15 | }, 16 | }, 17 | }, 18 | plugins: [], 19 | }; 20 | export default config; 21 | -------------------------------------------------------------------------------- /frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["dom", "dom.iterable", "esnext"], 4 | "allowJs": true, 5 | "skipLibCheck": true, 6 | "strict": true, 7 | "noEmit": true, 8 | "esModuleInterop": true, 9 | "module": "esnext", 10 | "moduleResolution": "bundler", 11 | "resolveJsonModule": true, 12 | "isolatedModules": true, 13 | "jsx": "preserve", 14 | "incremental": true, 15 | "plugins": [ 16 | { 17 | "name": "next" 18 | } 19 | ], 20 | "paths": { 21 | "@/*": ["./src/*"] 22 | } 23 | }, 24 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 25 | "exclude": ["node_modules"] 26 | } 27 | -------------------------------------------------------------------------------- /nginx/nginx.conf: -------------------------------------------------------------------------------- 1 | user nginx; 2 | worker_processes auto; 3 | 4 | error_log /var/log/nginx/error.log notice; 5 | pid /var/run/nginx.pid; 6 | 7 | 8 | events { 9 | worker_connections 1024; 10 | } 11 | 12 | 13 | http { 14 | include /etc/nginx/mime.types; 15 | default_type application/octet-stream; 16 | 17 | log_format main '$remote_addr - $remote_user [$time_local] "$request" ' 18 | '$status $body_bytes_sent "$http_referer" ' 19 | '"$http_user_agent" "$http_x_forwarded_for"'; 20 | 21 | access_log /var/log/nginx/access.log main; 22 | 23 | sendfile on; 24 | #tcp_nopush on; 25 | client_max_body_size 100M; # allow large file uploads 26 | 27 | keepalive_timeout 65; 28 | 29 | #gzip on; 30 | 31 | #include /etc/nginx/conf.d/*.conf; 32 | 33 | upstream admin { 34 | server admin:8000; 35 | } 36 | 37 | upstream api { 38 | server api:8000; 39 | } 40 | 41 | upstream frontend { 42 | server frontend:3000; 43 | } 44 | 45 | server { 46 | listen 80; 47 | 48 | location /admin { 49 | proxy_pass http://admin; 50 | proxy_set_header Host $host; 51 | proxy_set_header X-Real-IP $remote_addr; 52 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 53 | proxy_set_header X-Forwarded-Proto $scheme; 54 | } 55 | 56 | location /static { 57 | proxy_pass http://admin; 58 | proxy_set_header Host $host; 59 | proxy_set_header X-Real-IP $remote_addr; 60 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 61 | proxy_set_header X-Forwarded-Proto $scheme; 62 | } 63 | 64 | location /media { 65 | proxy_pass http://admin; 66 | proxy_set_header Host $host; 67 | proxy_set_header X-Real-IP $remote_addr; 68 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 69 | proxy_set_header X-Forwarded-Proto $scheme; 70 | } 71 | 72 | location /martor { 73 | proxy_pass http://admin; 74 | proxy_set_header Host $host; 75 | proxy_set_header X-Real-IP $remote_addr; 76 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 77 | proxy_set_header X-Forwarded-Proto $scheme; 78 | } 79 | 80 | location /openapi.json { 81 | proxy_pass http://api; 82 | proxy_set_header Host $host; 83 | proxy_set_header X-Real-IP $remote_addr; 84 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 85 | proxy_set_header X-Forwarded-Proto $scheme; 86 | } 87 | 88 | location /docs { 89 | proxy_pass http://api; 90 | proxy_set_header Host $host; 91 | proxy_set_header X-Real-IP $remote_addr; 92 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 93 | proxy_set_header X-Forwarded-Proto $scheme; 94 | } 95 | 96 | location ~ ^/api/v1(/?)(.*) { 97 | proxy_pass http://api/$2$is_args$args; 98 | proxy_set_header Host $host; 99 | proxy_set_header X-Real-IP $remote_addr; 100 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 101 | proxy_set_header X-Forwarded-Proto $scheme; 102 | } 103 | 104 | location / { 105 | proxy_pass http://frontend; 106 | proxy_http_version 1.1; 107 | proxy_set_header Upgrade $http_upgrade; 108 | proxy_set_header Connection 'upgrade'; 109 | proxy_set_header Host $host; 110 | proxy_cache_bypass $http_upgrade; 111 | } 112 | } 113 | } -------------------------------------------------------------------------------- /postgres/init.sql: -------------------------------------------------------------------------------- 1 | CREATE USER mysaas WITH PASSWORD 'mysaas'; 2 | CREATE DATABASE mysaas; 3 | GRANT ALL PRIVILEGES ON DATABASE mysaas TO mysaas; 4 | \connect mysaas; 5 | GRANT CREATE ON SCHEMA public TO mysaas; --------------------------------------------------------------------------------