Blog de Cuisine Self-Hosted

Dernière mise à jour le

#nextjs#react#postgresql#docker#fullstack#webdev

🍳 Concept

Un blog de cuisine entièrement self-hosted développé avec Next.js 14 et PostgreSQL, déployé en production sur mon homeserver. Le projet permet de créer, partager et rechercher des recettes avec un système d’authentification et d’approbation par admin.

URL de production: http://<SERVER_IP>:PORT | cuisine..com

🎯 Objectifs du Projet

  1. Self-hosting complet: Reprendre contrôle de mes données culinaires
  2. Apprentissage fullstack: Next.js App Router, PostgreSQL, Docker
  3. Authentification sécurisée: NextAuth.js avec système de rôles
  4. Performance: SSR pour SEO + CSR pour interactivité
  5. Production-ready: Docker Compose, healthchecks, rate limiting

🏗️ Architecture Technique

Stack

Frontend:      Next.js 14 (App Router)
Backend:       Next.js API Routes
Database:      PostgreSQL 15
ORM:           Prisma
Auth:          NextAuth.js v4
Styling:       Tailwind CSS
Deployment:    Docker Compose
Reverse Proxy: Nginx Proxy Manager

Infrastructure

Container PostgreSQL (cuisine_db)

  • Image: postgres:15-alpine
  • Port: 5432 (interne uniquement)
  • Volume: Data persistante
  • Healthcheck: pg_isready
  • Credentials: définis via variables d’environnement (non exposées ici)

Container Next.js (cuisine_app)

  • Image custom: Multi-stage build (node:18-bullseye)
  • Port: 3000
  • Environment: Production optimisé
  • Healthcheck: /api/health endpoint
  • Dépend de: PostgreSQL (wait-for-it)

🔐 Système d’Authentification

Workflow d’inscription

  1. Utilisateur s’inscrit (/auth/register)

    • Email + Password (validation stricte)
    • Compte créé avec approved=false
    • Cannot login until admin approval
  2. Admin approuve (manuellement via DB ou future admin panel)

    • Update approved=true
    • User peut maintenant se connecter
  3. Login (/auth/login)

    • NextAuth Credentials Provider
    • Password verification (bcryptjs)
    • JWT session créée (30 jours)
    • Role inclus dans token

Rôles Implémentés

RôlePermissions
USERLecture recettes, commentaires (futur)
CONTRIBUTORCréation recettes, modification propres recettes
ADMINTous droits, approbation utilisateurs, modération

Sécurité

  • Passwords: Hashés avec bcryptjs (10 rounds)
  • Sessions: JWT stockés en cookie httpOnly
  • CSRF: Protection NextAuth intégrée
  • Rate Limiting: 10 req/min sur auth, 30 req/min sur API
  • Validation: Zod schemas côté serveur

📄 Routes API

Authentication

POST /api/auth/register
- Body: { email, password, name }
- Returns: { user } ou error
- Rate limit: 10/min

POST /api/auth/[...nextauth] (login)
- Credentials: { email, password }
- Returns: Session JWT
- Rate limit: 10/min

GET /api/auth/[...nextauth] (session)
- Returns: Current user session

Recipes

GET /api/recipes
- Query: ?search=keyword&category=id&sort=recent|popular
- Returns: Recipe[] avec pagination
- Public
- Rate limit: 30/min

POST /api/recipes
- Body: { title, description, ingredients, steps, categoryId, ... }
- Returns: Created recipe
- Auth: CONTRIBUTOR+
- Rate limit: 30/min

GET /api/recipes/[id]
- Returns: Recipe détaillée + metadata
- Public

User

GET /api/user/me
- Returns: Current user info
- Auth: Required

GET /api/user/profile
- Returns: Full profile + stats
- Auth: Required

Health

GET /api/health
- Returns: { status: "ok" }
- Pas de rate limit

📱 Pages Implémentées

Public

Accueil (/)

  • Server-Side Rendering (SSR)
  • Affiche 6 recettes les plus récentes
  • Hero section avec présentation
  • Call-to-action inscription

Listing Recettes (/recipes)

  • Client-Side Rendering (CSR) pour interactivité
  • Recherche par keyword
  • Filtrage par catégorie
  • Tri: Récent, Populaire, Alphabétique
  • Infinite scroll (futur)

Détail Recette (/recipes/[id] - FUTUR)

  • SSR pour SEO optimal
  • Ingrédients + Steps structurés
  • Metadata (temps, portions, difficulté)
  • Commentaires (authenticated users)

Authentification

Inscription (/auth/register)

  • Formulaire avec validation temps réel
  • Feedback erreurs inline
  • Message “En attente d’approbation” après inscription

Connexion (/auth/login)

  • Redirect vers dashboard après login
  • Formulaire avec gestion erreurs
  • Link vers inscription

Authenticated

Dashboard (/dashboard)

  • Profil utilisateur
  • Statistiques personnelles
  • Mes recettes créées (CONTRIBUTOR)
  • Settings compte

🗄️ Schema Database (Prisma)

model User {
  id            String   @id @default(cuid())
  email         String   @unique
  password      String
  name          String?
  role          Role     @default(USER)
  approved      Boolean  @default(false)
  createdAt     DateTime @default(now())
  
  recipes       Recipe[]
  comments      Comment[]
}

enum Role {
  USER
  CONTRIBUTOR
  ADMIN
}

model Recipe {
  id            String   @id @default(cuid())
  title         String
  description   String
  ingredients   Json     // Array structured
  steps         Json     // Array structured
  prepTime      Int?     // minutes
  cookTime      Int?     // minutes
  servings      Int?
  difficulty    String?  // EASY, MEDIUM, HARD
  imageUrl      String?
  published     Boolean  @default(false)
  
  authorId      String
  author        User     @relation(fields: [authorId])
  
  categoryId    String
  category      Category @relation(fields: [categoryId])
  
  createdAt     DateTime @default(now())
  updatedAt     DateTime @updatedAt
  
  comments      Comment[]
}

model Category {
  id            String   @id @default(cuid())
  name          String   @unique
  slug          String   @unique
  description   String?
  
  recipes       Recipe[]
}

model Comment {
  id            String   @id @default(cuid())
  content       String
  
  authorId      String
  author        User     @relation(fields: [authorId])
  
  recipeId      String
  recipe        Recipe   @relation(fields: [recipeId])
  
  createdAt     DateTime @default(now())
}

🎨 Frontend Features

Composants Réutilisables

  • Header: Navigation avec auth state
  • Footer: Links + social
  • RecipeCard: Preview recette avec image
  • SearchBar: Input avec debounce
  • FilterDropdown: Catégories dynamiques
  • AuthForm: Formulaire login/register réutilisable

State Management

  • Server State: React Query (TanStack Query) - FUTUR
  • Local State: useState pour forms
  • Auth State: NextAuth useSession hook

Optimisations

  • Image Optimization: Next.js Image component
  • Code Splitting: Dynamic imports
  • CSS: Tailwind JIT compilation
  • Caching: SWR pour data fetching (futur)

🐳 Déploiement Docker

Multi-Stage Build

# Stage 1: Dependencies
FROM node:18-bullseye AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production

# Stage 2: Builder
FROM node:18-bullseye AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
ENV NEXT_TELEMETRY_DISABLED 1
RUN npx prisma generate
RUN npm run build

# Stage 3: Runner
FROM node:18-bullseye AS runner
WORKDIR /app
ENV NODE_ENV production
COPY --from=builder /app/public ./public
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
EXPOSE 3000
CMD ["node", "server.js"]

Docker Compose

version: '3.8'

services:
  postgres:
    image: postgres:15-alpine
    container_name: cuisine_db
    environment:
      POSTGRES_USER: "${POSTGRES_USER}"
      POSTGRES_PASSWORD: "${POSTGRES_PASSWORD}"
      POSTGRES_DB: cuisine_db
    volumes:
      - postgres_data:/var/lib/postgresql/data
    ports:
      - "5432:PORT"
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER}"]
      interval: 10s
      timeout: 5s
      retries: 5

  app:
    build:
      context: .
      dockerfile: Dockerfile
    container_name: cuisine_app
    ports:
      - "3000:PORT"
    environment:
      DATABASE_URL: "postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:PORT/cuisine_db"
      NEXTAUTH_SECRET: "${NEXTAUTH_SECRET}"
      NEXTAUTH_URL: "http://<SERVER_IP>:PORT"
    depends_on:
      postgres:
        condition: service_healthy
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:PORT/api/health"]
      interval: 30s
      timeout: 10s
      retries: 3

volumes:
  postgres_data:

📊 État Actuel (70% complet)

✅ Fonctionnalités Implémentées

  • Infrastructure Docker (PostgreSQL + Next.js)
  • Schema Prisma complet
  • Authentification NextAuth (register + login)
  • Système de rôles (USER/CONTRIBUTOR/ADMIN)
  • API Routes (recipes GET/POST, auth, user)
  • Rate limiting
  • Pages: Home, Recipes listing, Register, Login, Dashboard
  • Recherche et filtrage recettes
  • Healthcheck endpoint
  • Production deployment

⏳ En Cours / Prévu

  • Admin panel pour approuver users
  • Upload images recettes
  • Page détail recette (/recipes/[id])
  • Système de commentaires
  • Favoris/Bookmarks
  • Export PDF recettes
  • API: PATCH/DELETE recipes
  • Pagination API
  • Tests unitaires + E2E
  • Migration vers React Query
  • PWA support
  • Multi-langue (i18n)

🚀 Prochaines Étapes

Immédiat

  1. Admin Panel: Interface pour approuver nouveaux users
  2. Upload Images: Multer + Sharp pour resize/optimize
  3. Page Détail: Template complet recette individuelle

Court Terme

  1. Commentaires: Thread discussions sous recettes
  2. Favoris: System de bookmarks par user
  3. Export PDF: Générer PDF recette pour impression

Moyen Terme

  1. Search Avancée: Elasticsearch pour full-text search
  2. Recommandations: ML-based similar recipes
  3. Analytics: Tracking views, popular recipes

🎓 Apprentissages Clés

  1. Next.js App Router: Migration de Pages vers App Directory
  2. Prisma ORM: Schema design, relations, migrations
  3. NextAuth.js: Customisation providers, callbacks, sessions
  4. Docker Compose: Multi-container orchestration, healthchecks
  5. Rate Limiting: Middleware custom Next.js
  6. Security: OWASP best practices (hashing, validation, CSRF)
  7. DevOps: Production deployment, monitoring, logs

📝 Documentation Technique

Variables d’Environnement

# Database
DATABASE_URL="postgresql://user:pass@host:PORT/db"

# Auth
NEXTAUTH_SECRET="generate-with-openssl-rand-base64-32"
NEXTAUTH_URL="http://<SERVER_IP>:PORT"

# App
NODE_ENV="production"

Scripts npm

npm run dev         # Dev server (hot reload)
npm run build       # Production build
npm run start       # Start production server
npm run lint        # ESLint check
npx prisma studio   # Database GUI
npx prisma migrate dev  # Create migration

Commandes Docker

# Démarrer stack
docker compose up -d

# Rebuild après modifs
docker compose up -d --build

# Logs
docker compose logs -f app

# Accès DB
docker compose exec postgres psql -U cuisine_user -d cuisine_db

# Shutdown
docker compose down

🔗 Ressources


Status: 🟢 En production
Dernière mise à jour: 31 janvier 2025
Prochaine feature: Admin panel pour approbation users