A professional, enterprise-grade SaaS application built with Go (Gin), Supabase, HTMX, and Alpine.js. Features a powerful plugin architecture for easy extensibility.
- π Secure Authentication - Built-in authentication with Supabase Auth, supporting OAuth, SSO, and MFA
- π Plugin Architecture - Extensible plugin system for easy customization and feature additions
- β‘ Lightning Fast - Built with Go and HTMX for optimal performance with minimal JavaScript
- ποΈ Supabase Integration - Seamless database and real-time subscriptions
- π¨ Modern UI - Beautiful, responsive UI with Tailwind CSS and Alpine.js
- π± Responsive Design - Mobile-first design that works on all devices
- π Dark Mode - Built-in dark mode support
- π Security First - CSRF protection, rate limiting, security headers
- Backend: Go, Gin Framework
- Database & Auth: Supabase (PostgreSQL + Auth)
- Frontend: HTMX, Alpine.js, Tailwind CSS
- Icons: Lucide Icons
- Charts: Chart.js
saas-platform/
βββ cmd/api/ # Application entry point
βββ internal/
β βββ config/ # Configuration management
β βββ handlers/ # HTTP request handlers
β βββ middleware/ # Gin middleware
β βββ plugins/ # Plugin system core
β βββ supabase/ # Supabase client
βββ pkg/
β βββ logger/ # Structured logging
βββ plugins/ # Plugin implementations
β βββ dashboard/ # Dashboard plugin
β βββ users/ # User management plugin
β βββ settings/ # Settings plugin
βββ web/
β βββ static/ # Static assets
β βββ templates/ # HTML templates
βββ migrations/ # Database migrations
βββ scripts/ # Utility scripts
- Go 1.21 or higher
- Supabase account
- Node.js (for Tailwind CSS, optional)
- Clone the repository:
git clone https://github.com/your-org/saas-platform.git
cd saas-platform- Copy the environment file:
cp .env.example .env- Update the
.envfile with your Supabase credentials:
SUPABASE_URL=https://your-project.supabase.co
SUPABASE_KEY=your-supabase-anon-key
SUPABASE_JWT_SECRET=your-supabase-jwt-secret- Install dependencies:
go mod download- Run the application:
go run cmd/api/main.goThe application will be available at http://localhost:8080.
The platform includes a powerful plugin architecture that allows you to easily extend functionality.
- Create a new directory in
plugins/:
mkdir plugins/myplugin- Create a
myplugin.gofile:
package myplugin
import (
"context"
"net/http"
"github.com/enterprise/saas-platform/internal/config"
"github.com/enterprise/saas-platform/internal/plugins"
"github.com/enterprise/saas-platform/internal/supabase"
"github.com/enterprise/saas-platform/pkg/logger"
"github.com/gin-gonic/gin"
)
type Plugin struct {
*plugins.BasePlugin
client *supabase.Client
logger *logger.Logger
}
func NewMyPlugin() plugins.Plugin {
return &Plugin{
BasePlugin: plugins.NewBasePlugin(
"myplugin",
"1.0.0",
"My custom plugin",
"Your Name",
),
}
}
func (p *Plugin) Initialize(ctx context.Context, cfg *config.Config, client *supabase.Client, logger *logger.Logger) error {
p.client = client
p.logger = logger
return nil
}
func (p *Plugin) Shutdown(ctx context.Context) error {
return nil
}
func (p *Plugin) RegisterRoutes(router *gin.RouterGroup, authMiddleware gin.HandlerFunc) {
router.GET("/hello", authMiddleware, func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"message": "Hello from my plugin!"})
})
}
func (p *Plugin) GetMenuItems() []plugins.MenuItem {
return []plugins.MenuItem{
{
ID: "myplugin",
Label: "My Plugin",
Icon: "star",
Route: "/plugins/myplugin",
Position: 50,
},
}
}
func (p *Plugin) GetPermissions() []plugins.Permission {
return []plugins.Permission{
{
ID: "myplugin.view",
Name: "View My Plugin",
Description: "Access my plugin",
Category: "My Plugin",
},
}
}- Register your plugin in
cmd/api/main.go:
import "github.com/enterprise/saas-platform/plugins/myplugin"
// In main():
pluginRegistry.Register(myplugin.NewMyPlugin())All plugins must implement the following interface:
type Plugin interface {
// Metadata
Name() string
Version() string
Description() string
Author() string
// Lifecycle
Initialize(ctx context.Context, config *config.Config, client *supabase.Client, logger *logger.Logger) error
Shutdown(ctx context.Context) error
// Routes
RegisterRoutes(router *gin.RouterGroup, authMiddleware gin.HandlerFunc)
// Hooks
GetHooks() []Hook
// Menu items
GetMenuItems() []MenuItem
// Permissions
GetPermissions() []Permission
// Enabled
IsEnabled() bool
Enable()
Disable()
}Plugins can register hooks to respond to system events:
func (p *Plugin) GetHooks() []plugins.Hook {
return []plugins.Hook{
{
Name: "user.created",
Priority: 10,
Handler: func(data interface{}) error {
// Handle user creation
return nil
},
},
}
}Available hooks:
user.created- Fired when a new user is createduser.updated- Fired when a user is updateduser.login- Fired when a user logs insettings.updated- Fired when settings are updated
The platform uses Supabase Auth for authentication. Supported methods:
- Email/Password
- OAuth (GitHub, Google, etc.)
- Magic Links
- SSO (Enterprise plans)
// In your plugin
func (p *Plugin) RegisterRoutes(router *gin.RouterGroup, authMiddleware gin.HandlerFunc) {
// Public route
router.GET("/public", publicHandler)
// Protected route
router.GET("/private", authMiddleware, privateHandler)
// Role-based protection
router.GET("/admin", authMiddleware, middleware.RequireRole("admin"), adminHandler)
}The frontend uses HTMX for dynamic interactions and Alpine.js for reactivity.
<!-- Load content dynamically -->
<div hx-get="/api/data" hx-trigger="load">
Loading...
</div>
<!-- Form submission -->
<form hx-post="/api/submit" hx-swap="outerHTML">
<input type="text" name="data">
<button type="submit">Submit</button>
</form>
<!-- Infinite scroll -->
<div hx-get="/api/more" hx-trigger="revealed">
Load more...
</div><div x-data="{ open: false }">
<button @click="open = !open">Toggle</button>
<div x-show="open">Content</div>
</div>The platform uses Supabase (PostgreSQL) for data storage.
// Select data
users, err := client.Select(ctx, "users", []string{"id", "email", "name"}, map[string]interface{}{
"status": "active",
})
// Insert data
user, err := client.Insert(ctx, "users", map[string]interface{}{
"email": "user@example.com",
"name": "John Doe",
})
// Update data
updated, err := client.Update(ctx, "users", "user-id", map[string]interface{}{
"name": "Jane Doe",
})
// Delete data
err := client.Delete(ctx, "users", "user-id")The platform includes a structured logger with multiple levels:
logger.Debug("Debug message")
logger.Info("Info message")
logger.Warn("Warning message")
logger.Error("Error message")
logger.Fatal("Fatal message") // Exits the applicationRun tests:
go test ./...Run tests with coverage:
go test -cover ./...FROM golang:1.21-alpine AS builder
WORKDIR /app
COPY . .
RUN go build -o main cmd/api/main.go
FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=builder /app/main .
COPY --from=builder /app/web ./web
CMD ["./main"]Make sure to set all required environment variables in production:
SUPABASE_URLSUPABASE_KEYSUPABASE_JWT_SECRETSESSION_SECRET(change from default!)
This project is licensed under the MIT License - see the LICENSE file for details.
Contributions are welcome! Please read our Contributing Guide for details.
For support, email support@example.com or join our Slack channel.