Repository files navigation

buildair-framework

buildair-framework is one of the systems that lets you implement greenfield applications quickly — without spending the first days on infrastructure, auth boilerplate, or Docker setup. Clone it, rename things, and start building your actual product.

It is opinionated by design. The stack is fixed, the patterns are set, and the wiring is done. You bring the domain logic.


Stack

LayerTechnology
FrontendNuxt 4 (Vue 3, TypeScript)
BackendSymfony 8, PHP 8.4
DatabaseMySQL 8.4
AuthJWT via lexik/jwt-authentication-bundle
InfrastructurePodman / Docker Compose

What it already does

Authentication

  • User registration with email + password
  • Login returning a signed JWT (RS256, 1h TTL)
  • Protected routes via JWT on every GET/POST /api/* endpoint
  • /api/auth/me returns the current user's profile

Double-Opt-In (optional)

  • Flip DOUBLE_OPT_IN=true in .env to require email confirmation after registration
  • Verification link is sent via Symfony Mailer — plug in any SMTP provider
  • Unverified users are blocked at login via a UserChecker

User types

  • Normal vs. Admin — users carry ROLE_USER by default; ROLE_ADMIN can be granted and revoked
  • Free vs. Paying — a dedicated isPaying flag is independent of roles

Both attributes are controlled via Symfony console commands (see below), not through the API — intentionally, so your product code decides when and why status changes.

Database

  • Doctrine ORM with attribute-based mapping
  • Migrations are run automatically on container startup — no manual step needed
  • MySQL 8.4 LTS

Infrastructure

  • Single podman compose up -d --build brings up everything
  • JWT keys are auto-generated on first start
  • Apache passes Authorization headers correctly out of the box
  • Named volumes keep vendor/ and node_modules/ inside containers — host directory stays clean

How to use it (without forking)

This repository is a GitHub Template. Use it to create your own repo with a clean git history:

gh repo create my-company/my-app \
--template startwind/buildair-framework \
--private \
--clone

Or via the GitHub UI: click "Use this template""Create a new repository".

You get a fresh repository with all files but none of this repo's commit history.


Getting started

# 1. Copy the environment file and adjust values
cp .env.example .env
# 2. Start everything
podman compose up -d --build
# 3. Open the app
open http://localhost:4000

Services after startup:

ServiceURL
Frontendhttp://localhost:4000
Backend APIhttp://localhost:8000
MySQLlocalhost:3309

Console commands

# Grant / revoke admin role
podman compose exec backend php bin/console app:user:role user@example.com
podman compose exec backend php bin/console app:user:role user@example.com --revoke
# Grant / revoke paying status
podman compose exec backend php bin/console app:user:paying user@example.com
podman compose exec backend php bin/console app:user:paying user@example.com --revoke

Environment variables

VariableDefaultDescription
DOUBLE_OPT_INfalseRequire email verification after registration
MAILER_DSNnull://nullMailer transport — set to SMTP for real emails
APP_URLhttp://localhost:4000Frontend base URL (used in verification emails)
JWT_PASSPHRASE(set this)Passphrase for the RSA key pair
APP_SECRET(set this)Symfony app secret
DB_PASSWORDapp_passwordMySQL user password

API reference

MethodEndpointAuthDescription
POST/api/auth/registerRegister a new user
POST/api/auth/loginLogin, returns { token }
GET/api/auth/verify/{token}Confirm email address
GET/api/auth/meJWTCurrent user profile

All protected endpoints expect Authorization: Bearer <token>.


Project structure

├── backend/ Symfony 8 application
│ ├── src/
│ │ ├── Command/ app:user:role, app:user:paying
│ │ ├── Controller/ AuthController
│ │ ├── Entity/ User
│ │ ├── Security/ UserChecker
│ │ └── Service/ MailService
│ ├── migrations/
│ └── config/
├── frontend/ Nuxt 4 application
│ └── app/
│ ├── pages/ login, register, index, verify/[token]
│ ├── composables/ useAuth
│ └── middleware/ auth, guest
├── docker-compose.yml
└── .env.example

What to build next

This framework deliberately stops at auth and user management. From here, typical next steps are:

  • Add your domain entities and API endpoints in backend/src/
  • Add pages and composables in frontend/app/
  • Wire up a real mailer DSN for production (MAILER_DSN=smtp://...)
  • Tighten CORS in backend/config/packages/nelmio_cors.yaml before going live

About

Full-stack framework: Nuxt 4 + Symfony 8 + MySQL 8.4 with JWT auth

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Repository files navigation

buildair-framework

buildair-framework is one of the systems that lets you implement greenfield applications quickly — without spending the first days on infrastructure, auth boilerplate, or Docker setup. Clone it, rename things, and start building your actual product.

It is opinionated by design. The stack is fixed, the patterns are set, and the wiring is done. You bring the domain logic.


Stack

LayerTechnology
FrontendNuxt 4 (Vue 3, TypeScript)
BackendSymfony 8, PHP 8.4
DatabaseMySQL 8.4
AuthJWT via lexik/jwt-authentication-bundle
InfrastructurePodman / Docker Compose

What it already does

Authentication

  • User registration with email + password
  • Login returning a signed JWT (RS256, 1h TTL)
  • Protected routes via JWT on every GET/POST /api/* endpoint
  • /api/auth/me returns the current user's profile

Double-Opt-In (optional)

  • Flip DOUBLE_OPT_IN=true in .env to require email confirmation after registration
  • Verification link is sent via Symfony Mailer — plug in any SMTP provider
  • Unverified users are blocked at login via a UserChecker

User types

  • Normal vs. Admin — users carry ROLE_USER by default; ROLE_ADMIN can be granted and revoked
  • Free vs. Paying — a dedicated isPaying flag is independent of roles

Both attributes are controlled via Symfony console commands (see below), not through the API — intentionally, so your product code decides when and why status changes.

Database

  • Doctrine ORM with attribute-based mapping
  • Migrations are run automatically on container startup — no manual step needed
  • MySQL 8.4 LTS

Infrastructure

  • Single podman compose up -d --build brings up everything
  • JWT keys are auto-generated on first start
  • Apache passes Authorization headers correctly out of the box
  • Named volumes keep vendor/ and node_modules/ inside containers — host directory stays clean

How to use it (without forking)

This repository is a GitHub Template. Use it to create your own repo with a clean git history:

gh repo create my-company/my-app \
--template startwind/buildair-framework \
--private \
--clone

Or via the GitHub UI: click "Use this template""Create a new repository".

You get a fresh repository with all files but none of this repo's commit history.


Getting started

# 1. Copy the environment file and adjust values
cp .env.example .env
# 2. Start everything
podman compose up -d --build
# 3. Open the app
open http://localhost:4000

Services after startup:

ServiceURL
Frontendhttp://localhost:4000
Backend APIhttp://localhost:8000
MySQLlocalhost:3309

Console commands

# Grant / revoke admin role
podman compose exec backend php bin/console app:user:role user@example.com
podman compose exec backend php bin/console app:user:role user@example.com --revoke
# Grant / revoke paying status
podman compose exec backend php bin/console app:user:paying user@example.com
podman compose exec backend php bin/console app:user:paying user@example.com --revoke

Environment variables

VariableDefaultDescription
DOUBLE_OPT_INfalseRequire email verification after registration
MAILER_DSNnull://nullMailer transport — set to SMTP for real emails
APP_URLhttp://localhost:4000Frontend base URL (used in verification emails)
JWT_PASSPHRASE(set this)Passphrase for the RSA key pair
APP_SECRET(set this)Symfony app secret
DB_PASSWORDapp_passwordMySQL user password

API reference

MethodEndpointAuthDescription
POST/api/auth/registerRegister a new user
POST/api/auth/loginLogin, returns { token }
GET/api/auth/verify/{token}Confirm email address
GET/api/auth/meJWTCurrent user profile

All protected endpoints expect Authorization: Bearer <token>.


Project structure

├── backend/ Symfony 8 application
│ ├── src/
│ │ ├── Command/ app:user:role, app:user:paying
│ │ ├── Controller/ AuthController
│ │ ├── Entity/ User
│ │ ├── Security/ UserChecker
│ │ └── Service/ MailService
│ ├── migrations/
│ └── config/
├── frontend/ Nuxt 4 application
│ └── app/
│ ├── pages/ login, register, index, verify/[token]
│ ├── composables/ useAuth
│ └── middleware/ auth, guest
├── docker-compose.yml
└── .env.example

What to build next

This framework deliberately stops at auth and user management. From here, typical next steps are:

  • Add your domain entities and API endpoints in backend/src/
  • Add pages and composables in frontend/app/
  • Wire up a real mailer DSN for production (MAILER_DSN=smtp://...)
  • Tighten CORS in backend/config/packages/nelmio_cors.yaml before going live

About

Full-stack framework: Nuxt 4 + Symfony 8 + MySQL 8.4 with JWT auth

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

buildair-framework

buildair-framework is one of the systems that lets you implement greenfield applications quickly — without spending the first days on infrastructure, auth boilerplate, or Docker setup. Clone it, rename things, and start building your actual product.

It is opinionated by design. The stack is fixed, the patterns are set, and the wiring is done. You bring the domain logic.


Stack

LayerTechnology
FrontendNuxt 4 (Vue 3, TypeScript)
BackendSymfony 8, PHP 8.4
DatabaseMySQL 8.4
AuthJWT via lexik/jwt-authentication-bundle
InfrastructurePodman / Docker Compose

What it already does

Authentication

  • User registration with email + password
  • Login returning a signed JWT (RS256, 1h TTL)
  • Protected routes via JWT on every GET/POST /api/* endpoint
  • /api/auth/me returns the current user's profile

Double-Opt-In (optional)

  • Flip DOUBLE_OPT_IN=true in .env to require email confirmation after registration
  • Verification link is sent via Symfony Mailer — plug in any SMTP provider
  • Unverified users are blocked at login via a UserChecker

User types

  • Normal vs. Admin — users carry ROLE_USER by default; ROLE_ADMIN can be granted and revoked
  • Free vs. Paying — a dedicated isPaying flag is independent of roles

Both attributes are controlled via Symfony console commands (see below), not through the API — intentionally, so your product code decides when and why status changes.

Database

  • Doctrine ORM with attribute-based mapping
  • Migrations are run automatically on container startup — no manual step needed
  • MySQL 8.4 LTS

Infrastructure

  • Single podman compose up -d --build brings up everything
  • JWT keys are auto-generated on first start
  • Apache passes Authorization headers correctly out of the box
  • Named volumes keep vendor/ and node_modules/ inside containers — host directory stays clean

How to use it (without forking)

This repository is a GitHub Template. Use it to create your own repo with a clean git history:

gh repo create my-company/my-app \
--template startwind/buildair-framework \
--private \
--clone

Or via the GitHub UI: click "Use this template""Create a new repository".

You get a fresh repository with all files but none of this repo's commit history.


Getting started

# 1. Copy the environment file and adjust values
cp .env.example .env
# 2. Start everything
podman compose up -d --build
# 3. Open the app
open http://localhost:4000

Services after startup:

ServiceURL
Frontendhttp://localhost:4000
Backend APIhttp://localhost:8000
MySQLlocalhost:3309

Console commands

# Grant / revoke admin role
podman compose exec backend php bin/console app:user:role user@example.com
podman compose exec backend php bin/console app:user:role user@example.com --revoke
# Grant / revoke paying status
podman compose exec backend php bin/console app:user:paying user@example.com
podman compose exec backend php bin/console app:user:paying user@example.com --revoke

Environment variables

VariableDefaultDescription
DOUBLE_OPT_INfalseRequire email verification after registration
MAILER_DSNnull://nullMailer transport — set to SMTP for real emails
APP_URLhttp://localhost:4000Frontend base URL (used in verification emails)
JWT_PASSPHRASE(set this)Passphrase for the RSA key pair
APP_SECRET(set this)Symfony app secret
DB_PASSWORDapp_passwordMySQL user password

API reference

MethodEndpointAuthDescription
POST/api/auth/registerRegister a new user
POST/api/auth/loginLogin, returns { token }
GET/api/auth/verify/{token}Confirm email address
GET/api/auth/meJWTCurrent user profile

All protected endpoints expect Authorization: Bearer <token>.


Project structure

├── backend/ Symfony 8 application
│ ├── src/
│ │ ├── Command/ app:user:role, app:user:paying
│ │ ├── Controller/ AuthController
│ │ ├── Entity/ User
│ │ ├── Security/ UserChecker
│ │ └── Service/ MailService
│ ├── migrations/
│ └── config/
├── frontend/ Nuxt 4 application
│ └── app/
│ ├── pages/ login, register, index, verify/[token]
│ ├── composables/ useAuth
│ └── middleware/ auth, guest
├── docker-compose.yml
└── .env.example

What to build next

This framework deliberately stops at auth and user management. From here, typical next steps are:

  • Add your domain entities and API endpoints in backend/src/
  • Add pages and composables in frontend/app/
  • Wire up a real mailer DSN for production (MAILER_DSN=smtp://...)
  • Tighten CORS in backend/config/packages/nelmio_cors.yaml before going live

About

Full-stack framework: Nuxt 4 + Symfony 8 + MySQL 8.4 with JWT auth

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

buildair-framework

buildair-framework is one of the systems that lets you implement greenfield applications quickly — without spending the first days on infrastructure, auth boilerplate, or Docker setup. Clone it, rename things, and start building your actual product.

It is opinionated by design. The stack is fixed, the patterns are set, and the wiring is done. You bring the domain logic.


Stack

LayerTechnology
FrontendNuxt 4 (Vue 3, TypeScript)
BackendSymfony 8, PHP 8.4
DatabaseMySQL 8.4
AuthJWT via lexik/jwt-authentication-bundle
InfrastructurePodman / Docker Compose

What it already does

Authentication

  • User registration with email + password
  • Login returning a signed JWT (RS256, 1h TTL)
  • Protected routes via JWT on every GET/POST /api/* endpoint
  • /api/auth/me returns the current user's profile

Double-Opt-In (optional)

  • Flip DOUBLE_OPT_IN=true in .env to require email confirmation after registration
  • Verification link is sent via Symfony Mailer — plug in any SMTP provider
  • Unverified users are blocked at login via a UserChecker

User types

  • Normal vs. Admin — users carry ROLE_USER by default; ROLE_ADMIN can be granted and revoked
  • Free vs. Paying — a dedicated isPaying flag is independent of roles

Both attributes are controlled via Symfony console commands (see below), not through the API — intentionally, so your product code decides when and why status changes.

Database

  • Doctrine ORM with attribute-based mapping
  • Migrations are run automatically on container startup — no manual step needed
  • MySQL 8.4 LTS

Infrastructure

  • Single podman compose up -d --build brings up everything
  • JWT keys are auto-generated on first start
  • Apache passes Authorization headers correctly out of the box
  • Named volumes keep vendor/ and node_modules/ inside containers — host directory stays clean

How to use it (without forking)

This repository is a GitHub Template. Use it to create your own repo with a clean git history:

gh repo create my-company/my-app \
--template startwind/buildair-framework \
--private \
--clone

Or via the GitHub UI: click "Use this template""Create a new repository".

You get a fresh repository with all files but none of this repo's commit history.


Getting started

# 1. Copy the environment file and adjust values
cp .env.example .env
# 2. Start everything
podman compose up -d --build
# 3. Open the app
open http://localhost:4000

Services after startup:

ServiceURL
Frontendhttp://localhost:4000
Backend APIhttp://localhost:8000
MySQLlocalhost:3309

Console commands

# Grant / revoke admin role
podman compose exec backend php bin/console app:user:role user@example.com
podman compose exec backend php bin/console app:user:role user@example.com --revoke
# Grant / revoke paying status
podman compose exec backend php bin/console app:user:paying user@example.com
podman compose exec backend php bin/console app:user:paying user@example.com --revoke

Environment variables

VariableDefaultDescription
DOUBLE_OPT_INfalseRequire email verification after registration
MAILER_DSNnull://nullMailer transport — set to SMTP for real emails
APP_URLhttp://localhost:4000Frontend base URL (used in verification emails)
JWT_PASSPHRASE(set this)Passphrase for the RSA key pair
APP_SECRET(set this)Symfony app secret
DB_PASSWORDapp_passwordMySQL user password

API reference

MethodEndpointAuthDescription
POST/api/auth/registerRegister a new user
POST/api/auth/loginLogin, returns { token }
GET/api/auth/verify/{token}Confirm email address
GET/api/auth/meJWTCurrent user profile

All protected endpoints expect Authorization: Bearer <token>.


Project structure

├── backend/ Symfony 8 application
│ ├── src/
│ │ ├── Command/ app:user:role, app:user:paying
│ │ ├── Controller/ AuthController
│ │ ├── Entity/ User
│ │ ├── Security/ UserChecker
│ │ └── Service/ MailService
│ ├── migrations/
│ └── config/
├── frontend/ Nuxt 4 application
│ └── app/
│ ├── pages/ login, register, index, verify/[token]
│ ├── composables/ useAuth
│ └── middleware/ auth, guest
├── docker-compose.yml
└── .env.example

What to build next

This framework deliberately stops at auth and user management. From here, typical next steps are:

  • Add your domain entities and API endpoints in backend/src/
  • Add pages and composables in frontend/app/
  • Wire up a real mailer DSN for production (MAILER_DSN=smtp://...)
  • Tighten CORS in backend/config/packages/nelmio_cors.yaml before going live

About

Full-stack framework: Nuxt 4 + Symfony 8 + MySQL 8.4 with JWT auth

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Repository files navigation

buildair-framework

buildair-framework is one of the systems that lets you implement greenfield applications quickly — without spending the first days on infrastructure, auth boilerplate, or Docker setup. Clone it, rename things, and start building your actual product.

It is opinionated by design. The stack is fixed, the patterns are set, and the wiring is done. You bring the domain logic.


Stack

LayerTechnology
FrontendNuxt 4 (Vue 3, TypeScript)
BackendSymfony 8, PHP 8.4
DatabaseMySQL 8.4
AuthJWT via lexik/jwt-authentication-bundle
InfrastructurePodman / Docker Compose

What it already does

Authentication

  • User registration with email + password
  • Login returning a signed JWT (RS256, 1h TTL)
  • Protected routes via JWT on every GET/POST /api/* endpoint
  • /api/auth/me returns the current user's profile

Double-Opt-In (optional)

  • Flip DOUBLE_OPT_IN=true in .env to require email confirmation after registration
  • Verification link is sent via Symfony Mailer — plug in any SMTP provider
  • Unverified users are blocked at login via a UserChecker

User types

  • Normal vs. Admin — users carry ROLE_USER by default; ROLE_ADMIN can be granted and revoked
  • Free vs. Paying — a dedicated isPaying flag is independent of roles

Both attributes are controlled via Symfony console commands (see below), not through the API — intentionally, so your product code decides when and why status changes.

Database

  • Doctrine ORM with attribute-based mapping
  • Migrations are run automatically on container startup — no manual step needed
  • MySQL 8.4 LTS

Infrastructure

  • Single podman compose up -d --build brings up everything
  • JWT keys are auto-generated on first start
  • Apache passes Authorization headers correctly out of the box
  • Named volumes keep vendor/ and node_modules/ inside containers — host directory stays clean

How to use it (without forking)

This repository is a GitHub Template. Use it to create your own repo with a clean git history:

gh repo create my-company/my-app \
--template startwind/buildair-framework \
--private \
--clone

Or via the GitHub UI: click "Use this template""Create a new repository".

You get a fresh repository with all files but none of this repo's commit history.


Getting started

# 1. Copy the environment file and adjust values
cp .env.example .env
# 2. Start everything
podman compose up -d --build
# 3. Open the app
open http://localhost:4000

Services after startup:

ServiceURL
Frontendhttp://localhost:4000
Backend APIhttp://localhost:8000
MySQLlocalhost:3309

Console commands

# Grant / revoke admin role
podman compose exec backend php bin/console app:user:role user@example.com
podman compose exec backend php bin/console app:user:role user@example.com --revoke
# Grant / revoke paying status
podman compose exec backend php bin/console app:user:paying user@example.com
podman compose exec backend php bin/console app:user:paying user@example.com --revoke

Environment variables

VariableDefaultDescription
DOUBLE_OPT_INfalseRequire email verification after registration
MAILER_DSNnull://nullMailer transport — set to SMTP for real emails
APP_URLhttp://localhost:4000Frontend base URL (used in verification emails)
JWT_PASSPHRASE(set this)Passphrase for the RSA key pair
APP_SECRET(set this)Symfony app secret
DB_PASSWORDapp_passwordMySQL user password

API reference

MethodEndpointAuthDescription
POST/api/auth/registerRegister a new user
POST/api/auth/loginLogin, returns { token }
GET/api/auth/verify/{token}Confirm email address
GET/api/auth/meJWTCurrent user profile

All protected endpoints expect Authorization: Bearer <token>.


Project structure

├── backend/ Symfony 8 application
│ ├── src/
│ │ ├── Command/ app:user:role, app:user:paying
│ │ ├── Controller/ AuthController
│ │ ├── Entity/ User
│ │ ├── Security/ UserChecker
│ │ └── Service/ MailService
│ ├── migrations/
│ └── config/
├── frontend/ Nuxt 4 application
│ └── app/
│ ├── pages/ login, register, index, verify/[token]
│ ├── composables/ useAuth
│ └── middleware/ auth, guest
├── docker-compose.yml
└── .env.example

What to build next

This framework deliberately stops at auth and user management. From here, typical next steps are:

  • Add your domain entities and API endpoints in backend/src/
  • Add pages and composables in frontend/app/
  • Wire up a real mailer DSN for production (MAILER_DSN=smtp://...)
  • Tighten CORS in backend/config/packages/nelmio_cors.yaml before going live

About

Full-stack framework: Nuxt 4 + Symfony 8 + MySQL 8.4 with JWT auth

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

buildair-framework

buildair-framework is one of the systems that lets you implement greenfield applications quickly — without spending the first days on infrastructure, auth boilerplate, or Docker setup. Clone it, rename things, and start building your actual product.

It is opinionated by design. The stack is fixed, the patterns are set, and the wiring is done. You bring the domain logic.


Stack

LayerTechnology
FrontendNuxt 4 (Vue 3, TypeScript)
BackendSymfony 8, PHP 8.4
DatabaseMySQL 8.4
AuthJWT via lexik/jwt-authentication-bundle
InfrastructurePodman / Docker Compose

What it already does

Authentication

  • User registration with email + password
  • Login returning a signed JWT (RS256, 1h TTL)
  • Protected routes via JWT on every GET/POST /api/* endpoint
  • /api/auth/me returns the current user's profile

Double-Opt-In (optional)

  • Flip DOUBLE_OPT_IN=true in .env to require email confirmation after registration
  • Verification link is sent via Symfony Mailer — plug in any SMTP provider
  • Unverified users are blocked at login via a UserChecker

User types

  • Normal vs. Admin — users carry ROLE_USER by default; ROLE_ADMIN can be granted and revoked
  • Free vs. Paying — a dedicated isPaying flag is independent of roles

Both attributes are controlled via Symfony console commands (see below), not through the API — intentionally, so your product code decides when and why status changes.

Database

  • Doctrine ORM with attribute-based mapping
  • Migrations are run automatically on container startup — no manual step needed
  • MySQL 8.4 LTS

Infrastructure

  • Single podman compose up -d --build brings up everything
  • JWT keys are auto-generated on first start
  • Apache passes Authorization headers correctly out of the box
  • Named volumes keep vendor/ and node_modules/ inside containers — host directory stays clean

How to use it (without forking)

This repository is a GitHub Template. Use it to create your own repo with a clean git history:

gh repo create my-company/my-app \
--template startwind/buildair-framework \
--private \
--clone

Or via the GitHub UI: click "Use this template""Create a new repository".

You get a fresh repository with all files but none of this repo's commit history.


Getting started

# 1. Copy the environment file and adjust values
cp .env.example .env
# 2. Start everything
podman compose up -d --build
# 3. Open the app
open http://localhost:4000

Services after startup:

ServiceURL
Frontendhttp://localhost:4000
Backend APIhttp://localhost:8000
MySQLlocalhost:3309

Console commands

# Grant / revoke admin role
podman compose exec backend php bin/console app:user:role user@example.com
podman compose exec backend php bin/console app:user:role user@example.com --revoke
# Grant / revoke paying status
podman compose exec backend php bin/console app:user:paying user@example.com
podman compose exec backend php bin/console app:user:paying user@example.com --revoke

Environment variables

VariableDefaultDescription
DOUBLE_OPT_INfalseRequire email verification after registration
MAILER_DSNnull://nullMailer transport — set to SMTP for real emails
APP_URLhttp://localhost:4000Frontend base URL (used in verification emails)
JWT_PASSPHRASE(set this)Passphrase for the RSA key pair
APP_SECRET(set this)Symfony app secret
DB_PASSWORDapp_passwordMySQL user password

API reference

MethodEndpointAuthDescription
POST/api/auth/registerRegister a new user
POST/api/auth/loginLogin, returns { token }
GET/api/auth/verify/{token}Confirm email address
GET/api/auth/meJWTCurrent user profile

All protected endpoints expect Authorization: Bearer <token>.


Project structure

├── backend/ Symfony 8 application
│ ├── src/
│ │ ├── Command/ app:user:role, app:user:paying
│ │ ├── Controller/ AuthController
│ │ ├── Entity/ User
│ │ ├── Security/ UserChecker
│ │ └── Service/ MailService
│ ├── migrations/
│ └── config/
├── frontend/ Nuxt 4 application
│ └── app/
│ ├── pages/ login, register, index, verify/[token]
│ ├── composables/ useAuth
│ └── middleware/ auth, guest
├── docker-compose.yml
└── .env.example

What to build next

This framework deliberately stops at auth and user management. From here, typical next steps are:

  • Add your domain entities and API endpoints in backend/src/
  • Add pages and composables in frontend/app/
  • Wire up a real mailer DSN for production (MAILER_DSN=smtp://...)
  • Tighten CORS in backend/config/packages/nelmio_cors.yaml before going live

About

Full-stack framework: Nuxt 4 + Symfony 8 + MySQL 8.4 with JWT auth

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

buildair-framework

buildair-framework is one of the systems that lets you implement greenfield applications quickly — without spending the first days on infrastructure, auth boilerplate, or Docker setup. Clone it, rename things, and start building your actual product.

It is opinionated by design. The stack is fixed, the patterns are set, and the wiring is done. You bring the domain logic.


Stack

LayerTechnology
FrontendNuxt 4 (Vue 3, TypeScript)
BackendSymfony 8, PHP 8.4
DatabaseMySQL 8.4
AuthJWT via lexik/jwt-authentication-bundle
InfrastructurePodman / Docker Compose

What it already does

Authentication

  • User registration with email + password
  • Login returning a signed JWT (RS256, 1h TTL)
  • Protected routes via JWT on every GET/POST /api/* endpoint
  • /api/auth/me returns the current user's profile

Double-Opt-In (optional)

  • Flip DOUBLE_OPT_IN=true in .env to require email confirmation after registration
  • Verification link is sent via Symfony Mailer — plug in any SMTP provider
  • Unverified users are blocked at login via a UserChecker

User types

  • Normal vs. Admin — users carry ROLE_USER by default; ROLE_ADMIN can be granted and revoked
  • Free vs. Paying — a dedicated isPaying flag is independent of roles

Both attributes are controlled via Symfony console commands (see below), not through the API — intentionally, so your product code decides when and why status changes.

Database

  • Doctrine ORM with attribute-based mapping
  • Migrations are run automatically on container startup — no manual step needed
  • MySQL 8.4 LTS

Infrastructure

  • Single podman compose up -d --build brings up everything
  • JWT keys are auto-generated on first start
  • Apache passes Authorization headers correctly out of the box
  • Named volumes keep vendor/ and node_modules/ inside containers — host directory stays clean

How to use it (without forking)

This repository is a GitHub Template. Use it to create your own repo with a clean git history:

gh repo create my-company/my-app \
--template startwind/buildair-framework \
--private \
--clone

Or via the GitHub UI: click "Use this template""Create a new repository".

You get a fresh repository with all files but none of this repo's commit history.


Getting started

# 1. Copy the environment file and adjust values
cp .env.example .env
# 2. Start everything
podman compose up -d --build
# 3. Open the app
open http://localhost:4000

Services after startup:

ServiceURL
Frontendhttp://localhost:4000
Backend APIhttp://localhost:8000
MySQLlocalhost:3309

Console commands

# Grant / revoke admin role
podman compose exec backend php bin/console app:user:role user@example.com
podman compose exec backend php bin/console app:user:role user@example.com --revoke
# Grant / revoke paying status
podman compose exec backend php bin/console app:user:paying user@example.com
podman compose exec backend php bin/console app:user:paying user@example.com --revoke

Environment variables

VariableDefaultDescription
DOUBLE_OPT_INfalseRequire email verification after registration
MAILER_DSNnull://nullMailer transport — set to SMTP for real emails
APP_URLhttp://localhost:4000Frontend base URL (used in verification emails)
JWT_PASSPHRASE(set this)Passphrase for the RSA key pair
APP_SECRET(set this)Symfony app secret
DB_PASSWORDapp_passwordMySQL user password

API reference

MethodEndpointAuthDescription
POST/api/auth/registerRegister a new user
POST/api/auth/loginLogin, returns { token }
GET/api/auth/verify/{token}Confirm email address
GET/api/auth/meJWTCurrent user profile

All protected endpoints expect Authorization: Bearer <token>.


Project structure

├── backend/ Symfony 8 application
│ ├── src/
│ │ ├── Command/ app:user:role, app:user:paying
│ │ ├── Controller/ AuthController
│ │ ├── Entity/ User
│ │ ├── Security/ UserChecker
│ │ └── Service/ MailService
│ ├── migrations/
│ └── config/
├── frontend/ Nuxt 4 application
│ └── app/
│ ├── pages/ login, register, index, verify/[token]
│ ├── composables/ useAuth
│ └── middleware/ auth, guest
├── docker-compose.yml
└── .env.example

What to build next

This framework deliberately stops at auth and user management. From here, typical next steps are:

  • Add your domain entities and API endpoints in backend/src/
  • Add pages and composables in frontend/app/
  • Wire up a real mailer DSN for production (MAILER_DSN=smtp://...)
  • Tighten CORS in backend/config/packages/nelmio_cors.yaml before going live

About

Full-stack framework: Nuxt 4 + Symfony 8 + MySQL 8.4 with JWT auth

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Repository files navigation

buildair-framework

buildair-framework is one of the systems that lets you implement greenfield applications quickly — without spending the first days on infrastructure, auth boilerplate, or Docker setup. Clone it, rename things, and start building your actual product.

It is opinionated by design. The stack is fixed, the patterns are set, and the wiring is done. You bring the domain logic.


Stack

LayerTechnology
FrontendNuxt 4 (Vue 3, TypeScript)
BackendSymfony 8, PHP 8.4
DatabaseMySQL 8.4
AuthJWT via lexik/jwt-authentication-bundle
InfrastructurePodman / Docker Compose

What it already does

Authentication

  • User registration with email + password
  • Login returning a signed JWT (RS256, 1h TTL)
  • Protected routes via JWT on every GET/POST /api/* endpoint
  • /api/auth/me returns the current user's profile

Double-Opt-In (optional)

  • Flip DOUBLE_OPT_IN=true in .env to require email confirmation after registration
  • Verification link is sent via Symfony Mailer — plug in any SMTP provider
  • Unverified users are blocked at login via a UserChecker

User types

  • Normal vs. Admin — users carry ROLE_USER by default; ROLE_ADMIN can be granted and revoked
  • Free vs. Paying — a dedicated isPaying flag is independent of roles

Both attributes are controlled via Symfony console commands (see below), not through the API — intentionally, so your product code decides when and why status changes.

Database

  • Doctrine ORM with attribute-based mapping
  • Migrations are run automatically on container startup — no manual step needed
  • MySQL 8.4 LTS

Infrastructure

  • Single podman compose up -d --build brings up everything
  • JWT keys are auto-generated on first start
  • Apache passes Authorization headers correctly out of the box
  • Named volumes keep vendor/ and node_modules/ inside containers — host directory stays clean

How to use it (without forking)

This repository is a GitHub Template. Use it to create your own repo with a clean git history:

gh repo create my-company/my-app \
--template startwind/buildair-framework \
--private \
--clone

Or via the GitHub UI: click "Use this template""Create a new repository".

You get a fresh repository with all files but none of this repo's commit history.


Getting started

# 1. Copy the environment file and adjust values
cp .env.example .env
# 2. Start everything
podman compose up -d --build
# 3. Open the app
open http://localhost:4000

Services after startup:

ServiceURL
Frontendhttp://localhost:4000
Backend APIhttp://localhost:8000
MySQLlocalhost:3309

Console commands

# Grant / revoke admin role
podman compose exec backend php bin/console app:user:role user@example.com
podman compose exec backend php bin/console app:user:role user@example.com --revoke
# Grant / revoke paying status
podman compose exec backend php bin/console app:user:paying user@example.com
podman compose exec backend php bin/console app:user:paying user@example.com --revoke

Environment variables

VariableDefaultDescription
DOUBLE_OPT_INfalseRequire email verification after registration
MAILER_DSNnull://nullMailer transport — set to SMTP for real emails
APP_URLhttp://localhost:4000Frontend base URL (used in verification emails)
JWT_PASSPHRASE(set this)Passphrase for the RSA key pair
APP_SECRET(set this)Symfony app secret
DB_PASSWORDapp_passwordMySQL user password

API reference

MethodEndpointAuthDescription
POST/api/auth/registerRegister a new user
POST/api/auth/loginLogin, returns { token }
GET/api/auth/verify/{token}Confirm email address
GET/api/auth/meJWTCurrent user profile

All protected endpoints expect Authorization: Bearer <token>.


Project structure

├── backend/ Symfony 8 application
│ ├── src/
│ │ ├── Command/ app:user:role, app:user:paying
│ │ ├── Controller/ AuthController
│ │ ├── Entity/ User
│ │ ├── Security/ UserChecker
│ │ └── Service/ MailService
│ ├── migrations/
│ └── config/
├── frontend/ Nuxt 4 application
│ └── app/
│ ├── pages/ login, register, index, verify/[token]
│ ├── composables/ useAuth
│ └── middleware/ auth, guest
├── docker-compose.yml
└── .env.example

What to build next

This framework deliberately stops at auth and user management. From here, typical next steps are:

  • Add your domain entities and API endpoints in backend/src/
  • Add pages and composables in frontend/app/
  • Wire up a real mailer DSN for production (MAILER_DSN=smtp://...)
  • Tighten CORS in backend/config/packages/nelmio_cors.yaml before going live

About

Full-stack framework: Nuxt 4 + Symfony 8 + MySQL 8.4 with JWT auth

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages