DEV Community

Bipin C
Bipin C

Posted on

# Self-Hosted Push Notifications Part-1

Self-Hosted Push Notifications Specification

Part 1: Architecture & Setup Overview

Version: 1.0
Last Updated: October 2025
Author: Bunty9
License: MIT (Free to use and adapt)


Quick Links for all Posts

Part Title Topics Covered
Part 1 Architecture & Setup System overview, VAPID, database schema, project structure
Part 2 Backend Implementation GORM models, PushService, controllers, routes, middleware
Part 3 Frontend Implementation API routes, hooks, components, TypeScript types
Part 4 Service Worker & PWA SW implementation, PWA manifest, action handlers, offline
Part 5 Advanced & Deployment Targeting, scheduling, rate limiting, Docker, Kubernetes
Part 6 Monitoring & Debugging Metrics, logging, profiling, health checks, alerting
Part 7 Best Practices & Security Security, VAPID management, validation, optimization, testing
Part 8 Reference & FAQ API reference, browser compatibility, FAQ, troubleshooting

Table of Contents

  1. Introduction
  2. System Overview
  3. Architecture Components
  4. Technology Stack
  5. Prerequisites
  6. VAPID Key Generation
  7. Database Schema Design
  8. Project Structure
  9. Configuration Setup
  10. Next Steps

Introduction

What This Specification Covers

This is a complete, production-ready specification for implementing a self-hosted Web Push notification system without relying on third-party services like Firebase Cloud Messaging (FCM), OneSignal, Pusher, or similar platforms.

Why Self-Hosted Push Notifications?

Advantages:

  • Full Control - No vendor lock-in, complete customization
  • Zero Cost - No per-message pricing or monthly fees
  • Privacy - User data never leaves your infrastructure
  • Unlimited Scale - No artificial limits or quotas
  • Custom Logic - Advanced targeting, retry strategies, analytics

Trade-offs:

  • ⚠️ Infrastructure Responsibility - You manage the push gateway
  • ⚠️ Browser Limitations - Safari has limited support on iOS
  • ⚠️ Maintenance - Updates, monitoring, and debugging are your responsibility

Use Cases

This specification is ideal for:

  • SaaS Platforms - User dashboards, admin panels
  • Booking Systems - Reservations, confirmations, reminders
  • E-commerce - Order updates, delivery notifications
  • Project Management - Task assignments, status updates
  • Internal Tools - Employee dashboards, admin notifications
  • Any web application requiring real-time notifications

What You'll Build

By following this specification, you'll implement:

  1. Backend Push Service (Go)

    • VAPID-authenticated push gateway
    • Multi-device subscription management
    • Non-blocking goroutine-based sending
    • Advanced targeting strategies
    • Automatic failure handling and retry logic
    • Complete audit logging
  2. Frontend Integration (Next.js)

    • Service worker with background push handling
    • PWA configuration for installability
    • Subscription management UI
    • SSR API routes for secure backend communication
    • Type-safe payload structures
  3. Production Features

    • Multi-device support (up to 5 devices per user)
    • Targeted delivery (individual, role-based, broadcast)
    • Rich notifications (actions, images, sounds)
    • Offline support (notifications when app is closed)
    • Analytics and delivery tracking

System Overview

Web Push Protocol (RFC 8030)

Web Push is a browser standard that enables servers to send messages to web applications via push services. The flow works as follows:

┌──────────────┐        ┌──────────────┐        ┌──────────────┐        ┌──────────────┐
│              │        │              │        │              │        │              │
│   Backend    │───────▶│   Browser    │───────▶│ Push Service │───────▶│   User's     │
│   Server     │ VAPID  │   Endpoint   │ RFC    │ (Google/     │ Push   │   Browser    │
│   (Go)       │ Auth   │   (HTTPS)    │ 8030   │  Mozilla)    │ Event  │   (Client)   │
│              │        │              │        │              │        │              │
└──────────────┘        └──────────────┘        └──────────────┘        └──────────────┘
     Step 1                  Step 2                  Step 3                  Step 4
Enter fullscreen mode Exit fullscreen mode

Step 1: Your Go backend creates a signed payload with VAPID authentication
Step 2: Backend sends encrypted payload to browser's push endpoint (provided during subscription)
Step 3: Browser's push service (Google FCM for Chrome, Mozilla Push for Firefox) validates signature
Step 4: Push service delivers notification to user's browser, triggering service worker

VAPID (RFC 8292)

VAPID = Voluntary Application Server Identification

VAPID is the authentication mechanism that proves you own the application sending push notifications. It uses asymmetric cryptography (public/private key pairs):

  • Public Key - Shared with browsers during subscription (safe to expose)
  • Private Key - Used to sign push messages (keep secret)
  • Subject - Contact email (usually mailto:admin@yourdomain.com)

Why VAPID?

  • Prevents unauthorized parties from sending notifications
  • Allows push services to contact you if there's abuse
  • Required by modern browsers (Chrome 52+, Firefox 46+)

Architecture Diagram


Architecture Components

1. Backend Service (Go)

Responsibilities:

  • Generate and manage VAPID keys
  • Store and manage push subscriptions
  • Send push notifications via Web Push protocol
  • Handle delivery failures and retry logic
  • Log all notification events
  • Provide subscription management APIs

Key Libraries:

  • github.com/SherClockHolmes/webpush-go v1.3.0 - Web Push Protocol implementation
  • github.com/gin-gonic/gin - HTTP framework
  • gorm.io/gorm - ORM for database operations
  • Standard library: crypto/ecdsa, encoding/json

2. Frontend Application (Next.js)

Responsibilities:

  • Request notification permissions from users
  • Register service worker for background handling
  • Subscribe to push notifications via Push API
  • Display notification UI/badges
  • Handle notification clicks and actions
  • Manage subscription lifecycle

Key Technologies:

  • Next.js 13+ (Pages Router or App Router)
  • Service Worker API (navigator.serviceWorker)
  • Push API (PushManager, PushSubscription)
  • Notification API (Notification, ServiceWorkerRegistration.showNotification)

3. Service Worker

Responsibilities:

  • Listen for push events (even when app is closed)
  • Display notifications with custom UI
  • Handle notification clicks and actions
  • Manage notification lifecycle (show, close, replace)
  • Optional: Background sync, caching

Lifecycle:

Install → Activate → Idle → (Push Event) → Active → Idle
                       ▲──────────────────────┘
Enter fullscreen mode Exit fullscreen mode

4. Database

Tables Required:

  1. push_subscriptions - Stores device subscriptions

    • User/Admin identification
    • Endpoint URL (browser push service)
    • Encryption keys (p256dh, auth)
    • Device metadata (browser, OS, device name)
    • Health tracking (failure_count, is_active)
  2. push_notification_logs - Audit trail

    • Notification content
    • Delivery status (sent, failed, expired)
    • Timestamps and error messages
  3. notifications (Optional) - Persistent notification storage

    • For in-app notification center
    • Read/unread state
    • Archive/delete functionality

Technology Stack

Backend Requirements

Component Technology Version Purpose
Language Go 1.21+ Backend service
HTTP Framework Gin 1.9+ REST API endpoints
ORM GORM 1.25+ Database operations
Database PostgreSQL 14+ Data persistence
Push Library webpush-go 1.3.0 Web Push Protocol
Auth JWT - User authentication

Frontend Requirements

Component Technology Version Purpose
Framework Next.js 13+ React application
Language TypeScript 5+ Type safety
Service Worker Workbox (optional) 7+ SW management
PWA next-pwa (optional) 5+ PWA configuration

Browser Compatibility

Browser Desktop Mobile Notes
Chrome ✅ 52+ ✅ 52+ Full support
Firefox ✅ 46+ ✅ 46+ Full support
Edge ✅ 79+ ✅ 79+ Full support (Chromium-based)
Safari ✅ 16+ ⚠️ Limited iOS requires PWA installation first
Opera ✅ 39+ ✅ 39+ Full support (Chromium-based)

Important Safari Notes:

  • iOS Safari requires the app to be added to home screen (PWA)
  • No support for push notifications in regular mobile Safari browser
  • macOS Safari 16+ supports push notifications
  • Represents ~30% of users, consider fallback strategies

Prerequisites

Developer Skills Required

  • Go Programming - Intermediate level (goroutines, channels, error handling)
  • Next.js/React - Intermediate level (hooks, SSR/API routes)
  • TypeScript - Basic to intermediate
  • PostgreSQL - Basic (schema design, queries)
  • REST APIs - Understanding of HTTP methods, status codes
  • Web APIs - Service Workers, Push API, Notification API
  • Async JavaScript - Promises, async/await

Infrastructure Requirements

Development Environment:

- Go 1.21+ installed
- Node.js 18+ and npm/yarn
- PostgreSQL 14+ database
- Modern browser (Chrome/Firefox recommended)
- Code editor (VS Code recommended)
- Git for version control
Enter fullscreen mode Exit fullscreen mode

Production Environment:

- HTTPS domain (required for service workers)
- SSL certificate
- Container orchestration (Docker, Kubernetes) or VM
- PostgreSQL database (managed or self-hosted)
- Monitoring/logging solution
- Backup strategy
Enter fullscreen mode Exit fullscreen mode

Environment Setup Checklist

# 1. Check Go installation
go version  # Should be 1.21+

# 2. Check Node.js installation
node --version  # Should be 18+
npm --version

# 3. Check PostgreSQL installation
psql --version  # Should be 14+

# 4. Create project directory
mkdir my-push-notification-app
cd my-push-notification-app

# 5. Initialize Go module
mkdir backend
cd backend
go mod init github.com/yourusername/your-app

# 6. Initialize Next.js app
cd ..
npx create-next-app@latest frontend --typescript

# 7. Create PostgreSQL database
createdb your_app_db
Enter fullscreen mode Exit fullscreen mode

VAPID Key Generation

What Are VAPID Keys?

VAPID keys are ECDSA P-256 key pairs used to authenticate your server when sending push notifications. They prove that notifications are coming from your legitimate server.

Option 1: Generate with web-push CLI (Node.js)

Installation:

npm install -g web-push
Enter fullscreen mode Exit fullscreen mode

Generate Keys:

web-push generate-vapid-keys
Enter fullscreen mode Exit fullscreen mode

Output:

=======================================
Public Key:
BOzzMgOMwVyuziwHiI8Aw9sjs2jcxxC9ivxAGz2Qsue-Vty4lpqRDl6MWvGxJ5UTiNLrkYjHBYSKJCv4NYF1_0c

Private Key:
BNw7fc1ayj3-Az-OJ8DrOj3EDAHCORwO_r3SsrpwkzQ
=======================================
Enter fullscreen mode Exit fullscreen mode

Option 2: Generate with Go Script

Create generate_vapid.go:

package main

import (
    "fmt"
    "log"

    "github.com/SherClockHolmes/webpush-go"
)

func main() {
    privateKey, publicKey, err := webpush.GenerateVAPIDKeys()
    if err != nil {
        log.Fatalf("Failed to generate VAPID keys: %v", err)
    }

    fmt.Println("=======================================")
    fmt.Printf("Public Key:\n%s\n\n", publicKey)
    fmt.Printf("Private Key:\n%s\n", privateKey)
    fmt.Println("=======================================")
    fmt.Println("\nSave these keys securely!")
    fmt.Println("Public key: Add to frontend environment variables")
    fmt.Println("Private key: Add to backend environment variables (KEEP SECRET!)")
}
Enter fullscreen mode Exit fullscreen mode

Run:

# Install dependency
go get github.com/SherClockHolmes/webpush-go

# Generate keys
go run generate_vapid.go
Enter fullscreen mode Exit fullscreen mode

Storing VAPID Keys

Backend (.env or environment variables):

# REQUIRED: Keep private key secret!
VAPID_PRIVATE_KEY=BNw7fc1ayj3-Az-OJ8DrOj3EDAHCORwO_r3SsrpwkzQ
VAPID_PUBLIC_KEY=BOzzMgOMwVyuziwHiI8Aw9sjs2jcxxC9ivxAGz2Qsue-Vty4lpqRDl6MWvGxJ5UTiNLrkYjHBYSKJCv4NYF1_0c
VAPID_SUBJECT=mailto:admin@yourdomain.com  # Your contact email
Enter fullscreen mode Exit fullscreen mode

Frontend (.env.local):

# Safe to expose - used by browsers for subscription
NEXT_PUBLIC_VAPID_PUBLIC_KEY=BOzzMgOMwVyuziwHiI8Aw9sjs2jcxxC9ivxAGz2Qsue-Vty4lpqRDl6MWvGxJ5UTiNLrkYjHBYSKJCv4NYF1_0c
Enter fullscreen mode Exit fullscreen mode

Production Secrets (Kubernetes):

# Create secret
kubectl create secret generic vapid-keys \
  --from-literal=public-key='BOzzMgOMw...' \
  --from-literal=private-key='BNw7fc1ayj...' \
  --from-literal=subject='mailto:admin@yourdomain.com' \
  --namespace=your-namespace

# Reference in deployment
apiVersion: v1
kind: Pod
spec:
  containers:
  - name: backend
    env:
    - name: VAPID_PRIVATE_KEY
      valueFrom:
        secretKeyRef:
          name: vapid-keys
          key: private-key
Enter fullscreen mode Exit fullscreen mode

Key Security Best Practices

  1. Never commit private keys to git
   # Add to .gitignore
   .env
   .env.local
   .env.production
Enter fullscreen mode Exit fullscreen mode
  1. Use different keys for dev/staging/production

  2. Rotate keys periodically (every 6-12 months)

    • Generate new keys
    • Update backend/frontend env vars
    • Users will need to resubscribe (acceptable)
  3. Monitor for key compromise

    • Watch for unexpected push volume
    • Log all push operations
    • Alert on anomalies

Database Schema Design

Table 1: push_subscriptions

Purpose: Store push subscription details for each user device.

CREATE TABLE push_subscriptions (
    -- Primary Identification
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),

    -- User/Admin Association (mutually exclusive)
    user_id UUID REFERENCES users(id) ON DELETE CASCADE,
    admin_id UUID REFERENCES admins(id) ON DELETE CASCADE,

    -- Push Subscription Data (from browser)
    endpoint TEXT NOT NULL,  -- Browser push service URL (unique per device)
    p256dh_key TEXT NOT NULL,  -- Public key for encryption
    auth_key TEXT NOT NULL,    -- Authentication secret

    -- Device Metadata (for multi-device support)
    device_id VARCHAR(255),  -- Stable device identifier
    device_name VARCHAR(255),  -- Human-readable: "Chrome on MacBook Pro"
    device_type VARCHAR(50),   -- mobile, desktop, tablet
    browser VARCHAR(50),       -- Chrome, Firefox, Safari, Edge
    os VARCHAR(50),            -- Windows, macOS, Linux, Android, iOS
    user_agent TEXT,           -- Full user agent string

    -- Health & Lifecycle
    is_active BOOLEAN DEFAULT TRUE,
    failure_count INTEGER DEFAULT 0,  -- Increments on send failure
    last_used_at TIMESTAMP,           -- Last successful push

    -- Timestamps
    created_at TIMESTAMP DEFAULT NOW(),
    updated_at TIMESTAMP DEFAULT NOW(),

    -- Constraints
    CONSTRAINT check_user_or_admin CHECK (
        (user_id IS NOT NULL AND admin_id IS NULL) OR
        (user_id IS NULL AND admin_id IS NOT NULL)
    )
);

-- Indexes for Performance
CREATE INDEX idx_push_subscriptions_user_id ON push_subscriptions(user_id);
CREATE INDEX idx_push_subscriptions_admin_id ON push_subscriptions(admin_id);
CREATE INDEX idx_push_subscriptions_is_active ON push_subscriptions(is_active);
CREATE INDEX idx_push_subscriptions_endpoint ON push_subscriptions(endpoint);
CREATE INDEX idx_push_subscriptions_device_id ON push_subscriptions(device_id);
CREATE INDEX idx_push_subscriptions_last_used ON push_subscriptions(last_used_at);

-- Composite index for querying active subscriptions by user
CREATE INDEX idx_push_subscriptions_user_active
ON push_subscriptions(user_id, is_active)
WHERE is_active = TRUE;

-- Unique constraint on endpoint (one subscription per browser endpoint)
CREATE UNIQUE INDEX idx_push_subscriptions_endpoint_unique
ON push_subscriptions(endpoint);
Enter fullscreen mode Exit fullscreen mode

Table 2: push_notification_logs

Purpose: Audit trail for all push notification attempts.

CREATE TABLE push_notification_logs (
    -- Primary Identification
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),

    -- Relations
    subscription_id UUID REFERENCES push_subscriptions(id) ON DELETE SET NULL,
    user_id UUID,  -- Denormalized for faster querying
    admin_id UUID,

    -- Notification Content
    title VARCHAR(255),
    body TEXT,
    payload JSONB,  -- Full notification payload

    -- Delivery Status
    status VARCHAR(50) NOT NULL,  -- sent, failed, expired
    error_message TEXT,           -- Populated on failure
    http_status_code INTEGER,     -- Push service response code

    -- Timestamps
    sent_at TIMESTAMP DEFAULT NOW(),
    created_at TIMESTAMP DEFAULT NOW()
);

-- Indexes for Analytics
CREATE INDEX idx_push_notification_logs_subscription_id
ON push_notification_logs(subscription_id);

CREATE INDEX idx_push_notification_logs_status
ON push_notification_logs(status);

CREATE INDEX idx_push_notification_logs_sent_at
ON push_notification_logs(sent_at DESC);

CREATE INDEX idx_push_notification_logs_user_id
ON push_notification_logs(user_id);

-- Composite index for user-specific logs
CREATE INDEX idx_push_notification_logs_user_sent
ON push_notification_logs(user_id, sent_at DESC);
Enter fullscreen mode Exit fullscreen mode

Table 3: notifications (Optional)

Purpose: Persistent in-app notification storage (notification center).

CREATE TABLE notifications (
    -- Primary Identification
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),

    -- User/Admin Association
    user_id UUID REFERENCES users(id) ON DELETE CASCADE,
    admin_id UUID REFERENCES admins(id) ON DELETE CASCADE,

    -- Relations (optional, for filtering)
    booking_id UUID REFERENCES bookings(id) ON DELETE SET NULL,
    order_id UUID REFERENCES orders(id) ON DELETE SET NULL,
    -- Add other relations as needed

    -- Notification Content
    type VARCHAR(100) NOT NULL,  -- booking_confirmed, payment_received, etc.
    title VARCHAR(255) NOT NULL,
    message TEXT NOT NULL,
    data JSONB,  -- Additional contextual data

    -- UI Metadata
    read BOOLEAN DEFAULT FALSE,
    action_url TEXT,  -- Where to navigate on click
    priority VARCHAR(20) DEFAULT 'normal',  -- low, normal, high, urgent

    -- Timestamps
    created_at TIMESTAMP DEFAULT NOW(),
    updated_at TIMESTAMP DEFAULT NOW(),
    deleted_at TIMESTAMP  -- Soft delete
);

-- Indexes for Notification Center
CREATE INDEX idx_notifications_user_id ON notifications(user_id);
CREATE INDEX idx_notifications_admin_id ON notifications(admin_id);
CREATE INDEX idx_notifications_type ON notifications(type);
CREATE INDEX idx_notifications_read ON notifications(read);
CREATE INDEX idx_notifications_created_at ON notifications(created_at DESC);

-- Composite index for unread notifications
CREATE INDEX idx_notifications_user_unread
ON notifications(user_id, read, created_at DESC)
WHERE read = FALSE AND deleted_at IS NULL;
Enter fullscreen mode Exit fullscreen mode

Migration Scripts

Create Migration File: 001_create_push_subscriptions.sql

-- Up Migration
BEGIN;

CREATE TABLE IF NOT EXISTS push_subscriptions (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    user_id UUID REFERENCES users(id) ON DELETE CASCADE,
    admin_id UUID REFERENCES admins(id) ON DELETE CASCADE,
    endpoint TEXT NOT NULL,
    p256dh_key TEXT NOT NULL,
    auth_key TEXT NOT NULL,
    device_id VARCHAR(255),
    device_name VARCHAR(255),
    device_type VARCHAR(50),
    browser VARCHAR(50),
    os VARCHAR(50),
    user_agent TEXT,
    is_active BOOLEAN DEFAULT TRUE,
    failure_count INTEGER DEFAULT 0,
    last_used_at TIMESTAMP,
    created_at TIMESTAMP DEFAULT NOW(),
    updated_at TIMESTAMP DEFAULT NOW(),
    CONSTRAINT check_user_or_admin CHECK (
        (user_id IS NOT NULL AND admin_id IS NULL) OR
        (user_id IS NULL AND admin_id IS NOT NULL)
    )
);

CREATE INDEX idx_push_subscriptions_user_id ON push_subscriptions(user_id);
CREATE INDEX idx_push_subscriptions_admin_id ON push_subscriptions(admin_id);
CREATE INDEX idx_push_subscriptions_is_active ON push_subscriptions(is_active);
CREATE UNIQUE INDEX idx_push_subscriptions_endpoint_unique ON push_subscriptions(endpoint);

COMMIT;

-- Down Migration
BEGIN;
DROP TABLE IF EXISTS push_subscriptions CASCADE;
COMMIT;
Enter fullscreen mode Exit fullscreen mode

Apply Migration:

# Using psql
psql -U your_user -d your_database -f 001_create_push_subscriptions.sql

# Using GORM AutoMigrate (alternative)
db.AutoMigrate(&models.PushSubscription{})
Enter fullscreen mode Exit fullscreen mode

Project Structure

Recommended Backend Structure (Go)

backend/
├── cmd/
│   └── server/
│       └── main.go                    # Entry point
├── config/
│   ├── database.go                    # DB connection
│   └── vapid.go                       # VAPID key loading
├── models/
│   ├── push_subscription.go           # GORM model
│   └── push_notification_log.go       # Logging model
├── services/
│   └── push_service.go                # Core push logic
├── controllers/
│   └── push_controller.go             # HTTP handlers
├── routes/
│   └── push_routes.go                 # Route definitions
├── middleware/
│   └── auth.go                        # JWT validation
├── types/
│   └── push_types.go                  # Request/response types
├── utils/
│   ├── response.go                    # HTTP response helpers
│   └── device_detector.go             # Parse user agents
├── .env                               # Environment variables
├── go.mod                             # Go dependencies
└── go.sum
Enter fullscreen mode Exit fullscreen mode

Recommended Frontend Structure (Next.js)

frontend/
├── public/
│   ├── sw.js                          # Service worker (manual)
│   ├── manifest.json                  # PWA manifest
│   ├── icon-192x192.png               # PWA icons
│   └── icon-512x512.png
├── pages/
│   ├── _app.tsx                       # App wrapper
│   ├── _document.tsx                  # HTML document
│   ├── index.tsx                      # Home page
│   └── api/
│       └── push/
│           ├── subscribe.ts           # POST /api/push/subscribe
│           ├── unsubscribe.ts         # POST /api/push/unsubscribe
│           └── devices.ts             # GET /api/push/devices
├── components/
│   └── PushNotificationManager.tsx    # Subscription UI
├── hooks/
│   └── usePushNotifications.ts        # Push logic hook
├── types/
│   └── push.ts                        # TypeScript types
├── utils/
│   └── push-utils.ts                  # Helper functions
├── .env.local                         # Environment variables
├── next.config.js                     # Next.js config
├── package.json
└── tsconfig.json
Enter fullscreen mode Exit fullscreen mode

Configuration Setup

Backend Environment Variables

Create .env file:

# Server Configuration
PORT=8080
HOST=0.0.0.0
ENV=development  # development, staging, production

# Database Configuration
DB_HOST=localhost
DB_PORT=5432
DB_USER=your_user
DB_PASSWORD=your_password
DB_NAME=your_database
DB_SSLMODE=disable  # Use 'require' in production

# VAPID Configuration (generated earlier)
VAPID_PUBLIC_KEY=BOzzMgOMwVyuziwHiI8Aw9sjs2jcxxC9ivxAGz2Qsue-Vty4lpqRDl6MWvGxJ5UTiNLrkYjHBYSKJCv4NYF1_0c
VAPID_PRIVATE_KEY=BNw7fc1ayj3-Az-OJ8DrOj3EDAHCORwO_r3SsrpwkzQ
VAPID_SUBJECT=mailto:admin@yourdomain.com

# JWT Configuration (for authentication)
JWT_SECRET=your-jwt-secret-key-min-32-chars
JWT_EXPIRY=24h

# Rate Limiting (optional)
RATE_LIMIT_ENABLED=true
RATE_LIMIT_REQUESTS=100
RATE_LIMIT_WINDOW=1m

# Logging
LOG_LEVEL=info  # debug, info, warn, error
Enter fullscreen mode Exit fullscreen mode

Loading Configuration in Go:

package config

import (
    "fmt"
    "log"
    "os"

    "github.com/joho/godotenv"
)

type Config struct {
    Port           string
    DatabaseURL    string
    VAPIDPublicKey string
    VAPIDPrivateKey string
    VAPIDSubject   string
    JWTSecret      string
}

func Load() (*Config, error) {
    // Load .env file in development
    if os.Getenv("ENV") != "production" {
        if err := godotenv.Load(); err != nil {
            log.Println("No .env file found")
        }
    }

    config := &Config{
        Port: getEnv("PORT", "8080"),
        DatabaseURL: fmt.Sprintf(
            "host=%s port=%s user=%s password=%s dbname=%s sslmode=%s",
            getEnv("DB_HOST", "localhost"),
            getEnv("DB_PORT", "5432"),
            os.Getenv("DB_USER"),
            os.Getenv("DB_PASSWORD"),
            os.Getenv("DB_NAME"),
            getEnv("DB_SSLMODE", "disable"),
        ),
        VAPIDPublicKey: os.Getenv("VAPID_PUBLIC_KEY"),
        VAPIDPrivateKey: os.Getenv("VAPID_PRIVATE_KEY"),
        VAPIDSubject: os.Getenv("VAPID_SUBJECT"),
        JWTSecret: os.Getenv("JWT_SECRET"),
    }

    // Validate required configs
    if config.VAPIDPublicKey == "" || config.VAPIDPrivateKey == "" {
        return nil, fmt.Errorf("VAPID keys are required")
    }

    return config, nil
}

func getEnv(key, fallback string) string {
    if value := os.Getenv(key); value != "" {
        return value
    }
    return fallback
}
Enter fullscreen mode Exit fullscreen mode

Frontend Environment Variables

Create .env.local file:

# Backend API URL
NEXT_PUBLIC_API_URL=http://localhost:8080

# VAPID Public Key (same as backend)
NEXT_PUBLIC_VAPID_PUBLIC_KEY=BOzzMgOMwVyuziwHiI8Aw9sjs2jcxxC9ivxAGz2Qsue-Vty4lpqRDl6MWvGxJ5UTiNLrkYjHBYSKJCv4NYF1_0c

# Feature Flags (optional)
NEXT_PUBLIC_ENABLE_PUSH_NOTIFICATIONS=true
Enter fullscreen mode Exit fullscreen mode

Using Environment Variables in Next.js:

// utils/config.ts
export const config = {
    apiUrl: process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8080',
    vapidPublicKey: process.env.NEXT_PUBLIC_VAPID_PUBLIC_KEY || '',
    enablePushNotifications: process.env.NEXT_PUBLIC_ENABLE_PUSH_NOTIFICATIONS === 'true',
};

// Validate on build
if (!config.vapidPublicKey) {
    throw new Error('NEXT_PUBLIC_VAPID_PUBLIC_KEY is required');
}
Enter fullscreen mode Exit fullscreen mode

Next Steps

Now that you understand the architecture and have completed the initial setup, proceed to:

➡️ Part 2: Backend Implementation (Go)

Part 2 will cover:

  1. GORM models for push subscriptions
  2. Push service implementation with webpush-go
  3. Non-blocking goroutine-based sending
  4. Multi-device subscription management
  5. Advanced targeting strategies
  6. Error handling and automatic deactivation
  7. HTTP controllers and routes
  8. Complete code examples

Top comments (0)