A modern Nuxt 3 web application with capability-based permissions, PostgreSQL database, and Docker support.
- ✅ Nuxt 4.2.2 with Vue 3 and TypeScript
- ✅ PostgreSQL database with flexible connector architecture
- ✅ Capability-based permission system - code-first permissions that auto-sync to database
- ✅ Session-based authentication with secure HTTP-only cookies
- ✅ Internationalization (i18n) - English and German support with language switcher
- ✅ Admin panel with protected system resources
- User management (add, delete, reset passwords)
- Group management with membership control
- Permission assignment per group
- Application settings
- ✅ Email notifications (optional SMTP)
- ✅ Health monitoring with comprehensive health check endpoints
- ✅ Tailwind CSS for styling
- ✅ Docker Compose setup for production
- ✅ Vitest testing framework (45+ tests)
- DEV_QUICK_REFERENCE.md - Quick reference for developers
- DEV_GUIDE.md - Comprehensive developer guide with detailed explanations
- test/README.md - Testing documentation
Choose your preferred setup method:
- Docker Setup (Recommended) - Everything in containers
- Local Setup - Run on your machine with external PostgreSQL
- Docker 20.10+ and Docker Compose 2.0+
- Node.js 18+ and npm
- PostgreSQL 16+
# 1. Clone the repository
git clone <repository-url>
cd nuxt-starters
# 2. Start everything (app + database)
docker-compose up -d
# 3. Open your browser
# → http://localhost:3000That's it! The application will:
- Start PostgreSQL database
- Initialize database schema automatically
- Sync permissions from code
- Create default Admins group
- Make the app available at http://localhost:3000
# 1. Create environment file
cp .env.example .env
# 2. Edit .env with your settings (see Configuration section below)
nano .env
# 3. Start with custom config
docker-compose --env-file .env up -d
# 4. View logs
docker-compose logs -f app# Start services
docker-compose up -d
# Stop services
docker-compose down
# Stop and remove volumes (CAUTION: deletes data)
docker-compose down -v
# View logs
docker-compose logs -f
# Rebuild after code changes
docker-compose up -d --build
# Access database
docker exec -it app-db psql -U postgres -d app# Clone repository
git clone <repository-url>
cd nuxt-starters
# Install packages
npm installOption A: Use Docker for database only
# Start PostgreSQL container
docker run -d \
--name app-db \
-e POSTGRES_USER=postgres \
-e POSTGRES_PASSWORD=postgres \
-e POSTGRES_DB=app \
-p 5432:5432 \
postgres:16-alpine
# Or use docker-compose for just the database
docker-compose up -d dbOption B: Use existing PostgreSQL
# Connect to your PostgreSQL
psql -U postgres
# Create database and schema
CREATE DATABASE app;
\c app
CREATE SCHEMA app;# Create environment file
cp .env.example .envEdit .env with your database connection:
# Database Configuration (Required)
NUXT_DATABASE_TYPE=postgres
NUXT_DATABASE_HOST=localhost
NUXT_DATABASE_PORT=5432
NUXT_DATABASE_NAME=app
NUXT_DATABASE_USER=postgres
NUXT_DATABASE_PASSWORD=postgres
NUXT_DATABASE_SCHEMA=app
# Email Configuration (Optional - leave commented to disable)
# NUXT_EMAIL_HOST=smtp.gmail.com
# NUXT_EMAIL_PORT=587
# NUXT_EMAIL_USER=your-email@gmail.com
# NUXT_EMAIL_PASSWORD=your-app-password
# NUXT_EMAIL_FROM=app <noreply@example.com># Development mode (with hot reload)
npm run dev
# Production mode
npm run build
npm run startAll configuration uses the NUXT_ prefix for Nuxt's runtime config:
| Variable | Description | Default | Example |
|---|---|---|---|
NUXT_DATABASE_TYPE |
Database type | postgres |
postgres |
NUXT_DATABASE_HOST |
Database host | localhost |
localhost or db (Docker) |
NUXT_DATABASE_PORT |
Database port | 5432 |
5432 |
NUXT_DATABASE_NAME |
Database name | app |
app |
NUXT_DATABASE_USER |
Database user | postgres |
postgres |
NUXT_DATABASE_PASSWORD |
Database password | '' |
your_secure_password |
| Variable | Description | Default | Example |
|---|---|---|---|
NUXT_DATABASE_SCHEMA |
PostgreSQL schema | public |
app |
NUXT_DATABASE_SSL |
Enable SSL connection | false |
true |
NUXT_DATABASE_MAX_CONNECTIONS |
Connection pool size | 10 |
20 |
NUXT_DATABASE_IDLE_TIMEOUT |
Idle timeout (ms) | 30000 |
60000 |
NUXT_DATABASE_CONNECTION_TIMEOUT |
Connection timeout (ms) | 2000 |
5000 |
NUXT_PUBLIC_APP_NAME |
Application name | app |
My App |
Leave NUXT_EMAIL_HOST empty to disable email notifications:
| Variable | Description | Example |
|---|---|---|
NUXT_EMAIL_HOST |
SMTP server | smtp.gmail.com |
NUXT_EMAIL_PORT |
SMTP port | 587 |
NUXT_EMAIL_USER |
SMTP username | your-email@gmail.com |
NUXT_EMAIL_PASSWORD |
SMTP password | your-app-password |
NUXT_EMAIL_FROM |
From address | app <noreply@example.com> |
Development (.env):
NUXT_DATABASE_TYPE=postgres
NUXT_DATABASE_HOST=localhost
NUXT_DATABASE_PORT=5432
NUXT_DATABASE_NAME=app
NUXT_DATABASE_USER=postgres
NUXT_DATABASE_PASSWORD=postgres
NUXT_DATABASE_SCHEMA=appProduction (.env.production):
NODE_ENV=production
NUXT_DATABASE_TYPE=postgres
NUXT_DATABASE_HOST=your-production-host.com
NUXT_DATABASE_PORT=5432
NUXT_DATABASE_NAME=app_prod
NUXT_DATABASE_USER=app_user
NUXT_DATABASE_PASSWORD=strong_secure_password_here
NUXT_DATABASE_SCHEMA=app
NUXT_DATABASE_SSL=true
NUXT_DATABASE_MAX_CONNECTIONS=20
NUXT_EMAIL_HOST=smtp.gmail.com
NUXT_EMAIL_PORT=587
NUXT_EMAIL_USER=noreply@yourdomain.com
NUXT_EMAIL_PASSWORD=app_password
NUXT_EMAIL_FROM=app <noreply@yourdomain.com>Navigate to http://localhost:3000/users/register and create your account.
After registration:
- Login at http://localhost:3000/users/login
- Go to http://localhost:3000/users/admin
- The first registered user is automatically added to the Admins group
The application creates this default group on first startup:
- Admins (Protected system group)
- Has
admin.managepermission by default - Cannot be deleted or renamed
admin.managepermission cannot be removed
- Has
You can create additional groups through the admin panel at http://localhost:3000/users/admin.
# Install dependencies
npm install
# Build for production
npm run build
# Start production server
npm run startThe build output will be in .output/ directory.
# Build Docker image
docker build -t app:latest .
# Run container
docker run -d \
--name app \
-p 3000:3000 \
-e NUXT_DATABASE_HOST=your-db-host \
-e NUXT_DATABASE_PORT=5432 \
-e NUXT_DATABASE_NAME=app \
-e NUXT_DATABASE_USER=app_user \
-e NUXT_DATABASE_PASSWORD=secure_password \
-e NUXT_DATABASE_SSL=true \
app:latestBefore deploying to production:
- Set
NODE_ENV=production - Use strong database credentials
- Enable
NUXT_DATABASE_SSL=truefor remote databases - Configure firewall rules (allow only port 3000)
- Set up HTTPS with reverse proxy (nginx/traefik)
- Configure database backups
- Set up log rotation
- Monitor application health (
/api/health) - Use process manager (PM2) or container orchestration (Docker Swarm/Kubernetes)
The database schema is automatically initialized on first startup:
- Creates tables (users, sessions, groups, user_groups, permissions)
- Creates indexes for performance
- Creates default Admins group
- Syncs permissions from code to database
Location: server/database/schema.ts
users
├── id (SERIAL PRIMARY KEY)
├── username (VARCHAR UNIQUE)
├── email (VARCHAR UNIQUE)
├── password (VARCHAR)
├── created_at (TIMESTAMP)
└── updated_at (TIMESTAMP)
sessions
├── id (VARCHAR PRIMARY KEY)
├── user_id (INTEGER → users.id)
├── expires_at (TIMESTAMP)
└── created_at (TIMESTAMP)
groups
├── id (SERIAL PRIMARY KEY)
├── name (VARCHAR UNIQUE)
├── description (TEXT)
├── created_at (TIMESTAMP)
└── updated_at (TIMESTAMP)
user_groups (junction table)
├── user_id (INTEGER → users.id)
├── group_id (INTEGER → groups.id)
├── joined_at (TIMESTAMP)
└── PRIMARY KEY (user_id, group_id)
permissions
├── id (SERIAL PRIMARY KEY)
├── group_id (INTEGER → groups.id)
├── permission_key (VARCHAR)
├── created_at (TIMESTAMP)
└── UNIQUE (group_id, permission_key)
Docker:
# Access PostgreSQL CLI
docker exec -it app-db psql -U postgres -d app
# Set schema
\c app
SET search_path TO app, public;
# List tables
\dt
# Query users
SELECT * FROM users;Local:
psql -U postgres -d app
SET search_path TO app, public;Backup:
# Docker
docker exec app-db pg_dump -U postgres app > backup.sql
# Local
pg_dump -U postgres -d app > backup.sqlRestore:
# Docker
docker exec -i app-db psql -U postgres -d app < backup.sql
# Local
psql -U postgres -d app < backup.sql# All tests
npm run test
# With coverage
npm run test:coverage
# Watch mode
npm run test:watch
# Specific file
npm run test -- users.test.ts/
├── app/ # Client application
│ ├── assets/css/ # Tailwind styles
│ ├── components/ # Vue components
│ │ └── admin/ # Admin UI components
│ ├── composables/ # Client composables
│ │ └── useUserGroups.ts # Permission management
│ ├── layouts/ # Layouts
│ │ └── default.vue # Main layout with nav
│ ├── middleware/ # Route guards
│ │ └── permissions.global.ts # Permission checks
│ ├── pages/ # File-based routing
│ │ ├── index.vue # Home
│ │ ├── permissions.vue # Permissions list - Protected page
│ │ └── users/ # User pages
│ └── plugins/ # Vue plugins
│ └── permissions.ts # v-can directive
│
├── server/ # Server application
│ ├── api/ # API endpoints
│ │ ├── users/ # Authentication
│ │ ├── groups/ # Group management
│ │ ├── permissions/ # Permission management
│ │ └── settings/ # App settings
│ ├── composables/ # Server composables
│ │ ├── useDatabase.ts # DB utilities
│ │ ├── useUsers.ts # User queries
│ │ └── usePermissions.ts # Permission queries
│ ├── config/ # Configuration
│ │ └── permissions.ts # Permission registry
│ ├── database/ # Database layer
│ │ ├── schema.ts # Table definitions
│ │ ├── connector-factory.ts # DB factory
│ │ └── connectors/ # Database drivers
│ ├── plugins/ # Server plugins
│ │ └── database.ts # DB initialization
│ └── utils/ # Utilities
│ ├── auth.ts # Password hashing
│ ├── session.ts # Session management
│ └── sync-permissions.ts # Permission sync
│
├── test/ # Test suite
│ ├── server/api/ # API tests
│ └── utils/test-helpers.ts # Test utilities
│
├── .env # Environment variables
├── docker-compose.yml # Docker setup
├── Dockerfile # App container
├── nuxt.config.ts # Nuxt config
└── vitest.config.ts # Test config
Error: "Connection refused" or "Cannot connect to database"
Solutions:
-
Check if PostgreSQL is running:
# Docker docker ps | grep postgres # Local pg_isready -h localhost -p 5432
-
Verify
.envconfiguration matches your database -
For Docker, ensure
NUXT_DATABASE_HOST=db(service name in docker-compose) -
For local, ensure
NUXT_DATABASE_HOST=localhost
Error: "403 Permission denied" when accessing admin panel
Solutions:
-
Check user is in Admins group:
SELECT u.username, g.name FROM users u JOIN user_groups ug ON u.id = ug.user_id JOIN groups g ON ug.group_id = g.id WHERE u.username = 'your-username';
-
Verify Admins group has
admin.managepermission:SELECT g.name, p.permission_key FROM permissions p JOIN groups g ON p.group_id = g.id WHERE g.name = 'Admins';
-
Clear session and login again
Tip: For pages that need multiple permissions, consider using component-level protection instead of route protection. This allows users to see sections they have access to rather than blocking the entire page. See DEV_GUIDE.md for examples.
Issue: Email notifications not working
Solutions:
- Check SMTP configuration in
.env - For Gmail, use App Password
- Verify email settings are enabled in admin panel
- Check server logs for errors:
# Docker docker-compose logs app
Error: "Port 3000 is already in use"
Solutions:
# Find process using port 3000
lsof -i :3000
# Kill process
kill -9 <PID>
# Or change port in docker-compose.yml
ports:
- "3001:3000"Error: "Schema app does not exist"
Solutions:
- Verify
NUXT_DATABASE_SCHEMA=appin.env - Create schema manually:
CREATE SCHEMA app;
- Restart application to initialize tables
The application supports multiple languages with automatic detection and user preference storage.
- 🇬🇧 English (default)
- 🇩🇪 German (Deutsch)
- Language switcher in the header (right side)
- Browser detection - Automatically uses browser language on first visit
- Cookie persistence - Remembers user's language preference
- Clean URLs - No
/enor/deprefix (usesno_prefixstrategy) - Full coverage - All pages and components translated
- Click the language dropdown in the top-right corner
- Select your preferred language (EN or DE)
- The choice is saved in a cookie and persists across sessions
Translation files are located in i18n/locales/:
en.json- English translationsde.json- German translations (Deutsch)
Using translations in templates:
<template>
<h1>{{ $t('auth.login.title') }}</h1>
<button>{{ $t('common.save') }}</button>
</template>Using translations in scripts:
const { t } = useI18n()
const message = t('auth.login.title')Adding new translations:
- Add keys to both
en.jsonandde.json - Use dot notation for organization:
section.subsection.key - Keep structure consistent across all language files
Adding a new language:
- Create
i18n/locales/xx.json(e.g.,fr.jsonfor French) - Add locale config in nuxt.config.ts:
{ code: 'fr', iso: 'fr-FR', name: 'Français', files: ['fr.json'] }
- Copy translations from
en.jsonand translate
See DEV_GUIDE.md for complete i18n documentation.
POST /api/users/register- Register new userPOST /api/users/login- LoginPOST /api/users/logout- LogoutGET /api/users/me- Get current user with permissions
GET /api/users/admin/list- List all usersPOST /api/users/admin/add- Add new userPOST /api/users/admin/delete- Delete userPOST /api/users/admin/reset-password- Reset password
GET /api/groups/list- List all groupsPOST /api/groups/create- Create groupPATCH /api/groups/update- Update groupPOST /api/groups/delete- Delete group (except Admins)GET /api/groups/members- Get group membersPOST /api/groups/add-member- Add user to groupPOST /api/groups/remove-member- Remove user from group
GET /api/permissions/registered- Get all registered permissionsGET /api/permissions/group/[id]- Get permissions for groupPOST /api/permissions/add- Add permission to groupPOST /api/permissions/remove- Remove permission from group
GET /api/settings- Get public settingsPATCH /api/settings/update- Update settings (admin only)
GET /api/health- Comprehensive health check with all dependenciesGET /api/healthz- Simple liveness check (fast, no dependencies)GET /api/ready- Readiness check for load balancers
Health Check Response Example:
{
"status": "healthy",
"timestamp": "2026-01-01T12:00:00.000Z",
"responseTime": "15ms",
"checks": {
"database": {
"status": "up",
"responseTime": "12ms"
},
"email": {
"status": "configured",
"critical": false
},
"application": {
"status": "up",
"name": "My App",
"version": "1.0.0",
"environment": "production",
"uptime": 3600
},
"system": {
"memory": {
"used": "45MB",
"total": "128MB"
}
}
}
}For Coolify/Docker: Configure health check in docker-compose.yml:
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/api/healthz"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40sSee DEV_QUICK_REFERENCE.md for detailed examples.
This project is open source and available under the MIT License.
- Quick Reference: DEV_QUICK_REFERENCE.md
- Developer Guide: DEV_GUIDE.md
- Nuxt Docs: https://nuxt.com/docs
- PostgreSQL Docs: https://www.postgresql.org/docs/