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
- Introduction
- System Overview
- Architecture Components
- Technology Stack
- Prerequisites
- VAPID Key Generation
- Database Schema Design
- Project Structure
- Configuration Setup
- 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:
-
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
-
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
-
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
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
▲──────────────────────┘
4. Database
Tables Required:
-
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)
-
push_notification_logs - Audit trail
- Notification content
- Delivery status (sent, failed, expired)
- Timestamps and error messages
-
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
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
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
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
Generate Keys:
web-push generate-vapid-keys
Output:
=======================================
Public Key:
BOzzMgOMwVyuziwHiI8Aw9sjs2jcxxC9ivxAGz2Qsue-Vty4lpqRDl6MWvGxJ5UTiNLrkYjHBYSKJCv4NYF1_0c
Private Key:
BNw7fc1ayj3-Az-OJ8DrOj3EDAHCORwO_r3SsrpwkzQ
=======================================
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!)")
}
Run:
# Install dependency
go get github.com/SherClockHolmes/webpush-go
# Generate keys
go run generate_vapid.go
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
Frontend (.env.local
):
# Safe to expose - used by browsers for subscription
NEXT_PUBLIC_VAPID_PUBLIC_KEY=BOzzMgOMwVyuziwHiI8Aw9sjs2jcxxC9ivxAGz2Qsue-Vty4lpqRDl6MWvGxJ5UTiNLrkYjHBYSKJCv4NYF1_0c
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
Key Security Best Practices
- ✅ Never commit private keys to git
# Add to .gitignore
.env
.env.local
.env.production
✅ Use different keys for dev/staging/production
-
✅ Rotate keys periodically (every 6-12 months)
- Generate new keys
- Update backend/frontend env vars
- Users will need to resubscribe (acceptable)
-
✅ 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);
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);
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;
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;
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{})
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
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
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
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
}
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
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');
}
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:
- GORM models for push subscriptions
- Push service implementation with webpush-go
- Non-blocking goroutine-based sending
- Multi-device subscription management
- Advanced targeting strategies
- Error handling and automatic deactivation
- HTTP controllers and routes
- Complete code examples
Top comments (0)