Skip to main content

Framework Architecture

Developer Intermediate

Understanding EZ-Console's architecture is essential for building robust applications. This guide explains the framework's design, components, and how they interact.

High-Level Architecture​

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Frontend Layer β”‚
β”‚ React + TypeScript + Ant Design + React Router β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚ HTTP/HTTPS (JSON)
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ API Gateway Layer β”‚
β”‚ Gin Router + Middleware (Auth, CORS, Log) β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Controller Layer β”‚
β”‚ HTTP Request Handling + Input Validation β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Service Layer β”‚
β”‚ Business Logic + Transaction Management β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Data Access Layer β”‚
β”‚ GORM ORM + Database Operations β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Database β”‚
β”‚ SQLite / MySQL / PostgreSQL β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Backend Architecture​

Layered Architecture​

EZ-Console backend follows a clean, layered architecture:

1. Controller Layer​

Responsibility: HTTP request handling

type UserController struct {
svc server.Service
}

func (c *UserController) GetUser(ctx *gin.Context) {
userID := ctx.Param("id")

// Input validation
if userID == "" {
util.RespondWithError(ctx, util.NewErrorMessage("E4001", "User ID is required"))
return
}

// Call service layer
user, err := c.svc.User().GetUserByID(ctx.Request.Context(), userID)
if err != nil {
util.RespondWithError(ctx, util.NewErrorMessage("E5001", "Failed to get user", err))
return
}

// Return response
util.RespondWithSuccess(ctx, http.StatusOK, user)
}

Key Points:

  • Handles HTTP-specific concerns
  • Validates input parameters
  • Calls service layer for business logic
  • Formats and returns responses
  • Does NOT contain business logic

2. Service Layer​

Responsibility: Business logic and orchestration

type UserService struct {
db *gorm.DB
}

func (s *UserService) GetUserByID(ctx context.Context, resourceID string) (*model.User, error) {
logger := log.GetContextLogger(ctx)

var user model.User
err := s.db.Where("resource_id = ?", resourceID).
Preload("Roles").
First(&user).Error

if err != nil {
logger.Log("msg", "failed to get user", "err", err, "resource_id", resourceID)
return nil, err
}

return &user, nil
}

Key Points:

  • Contains pure business logic
  • Performs database operations
  • Manages transactions
  • Can call other services
  • Returns domain models

3. Model Layer​

Responsibility: Data structure definition

type User struct {
Base
Username string `gorm:"uniqueIndex;size:64" json:"username"`
Email string `gorm:"uniqueIndex;size:128" json:"email"`
FullName string `gorm:"size:128" json:"full_name"`
PasswordHash string `gorm:"size:128" json:"-"`
Status UserStatus `gorm:"default:active" json:"status"`
Roles []Role `gorm:"many2many:user_roles;" json:"roles"`
LastLoginAt *time.Time `json:"last_login_at"`
}

Key Points:

  • Defines database schema
  • Uses GORM tags for ORM mapping
  • Includes validation rules
  • Defines relationships

4. Middleware Layer​

Responsibility: Cross-cutting concerns

func RequireAuth() gin.HandlerFunc {
return func(ctx *gin.Context) {
token := extractToken(ctx)

claims, err := jwt.ValidateToken(token)
if err != nil {
util.RespondWithError(ctx, util.NewErrorMessage("E4012", "Invalid auth token"))
ctx.Abort()
return
}

// Store user info in context
ctx.Set("user_id", claims.UserID)
ctx.Next()
}
}

Built-in Middleware:

  • Authentication: JWT token validation
  • Permission: RBAC permission checking
  • Logging: Structured request/response logging
  • CORS: Cross-origin resource sharing
  • Recovery: Panic recovery
  • Metrics: Request metrics collection

Request Flow​

Here's how a typical request flows through the backend:

1. Client Request
↓
2. Gin Router (route matching)
↓
3. Middleware Chain
- Logging (start)
- CORS headers
- Authentication (JWT validation)
- Permission check
↓
4. Controller
- Parse request parameters
- Validate input
- Call service
↓
5. Service
- Execute business logic
- Database operations
- Call other services if needed
↓
6. Model/Repository
- GORM query execution
- Data persistence
↓
7. Controller (continued)
- Format response
- Return HTTP response
↓
8. Middleware Chain (reversed)
- Logging (end)
- Metrics collection
↓
9. Client Response

Frontend Architecture​

Component Hierarchy​

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Application Shell β”‚
β”‚ (Layout, Navigation, Authentication) β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ β”‚
β”Œβ”€β”€β”€β–Όβ”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”
β”‚ Public β”‚ β”‚ Private β”‚
β”‚ Routes β”‚ β”‚ Routes β”‚
β”‚ β”‚ β”‚ (Protected) β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜
β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ β”‚ β”‚
β”Œβ”€β”€β”€β”€β–Όβ”€β”€β”€β” β”Œβ”€β”€β”€β–Όβ”€β”€β”€β”€β” β”Œβ”€β”€β”€β–Όβ”€β”€β”€β”€β”
β”‚ Page β”‚ β”‚ Page β”‚ β”‚ Page β”‚
β”‚Componentsβ”‚ β”‚Componentsβ”‚ β”‚Componentsβ”‚
β””β”€β”€β”€β”€β”¬β”€β”€β”€β”˜ β””β”€β”€β”€β”¬β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”¬β”€β”€β”€β”€β”˜
β”‚ β”‚ β”‚
β”Œβ”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”
β”‚ Shared Components β”‚
β”‚ (Tables, Forms, Modals) β”‚
β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚
β”Œβ”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ API Service Layer β”‚
β”‚ (HTTP Client + Interceptors)β”‚
β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚
β”Œβ”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ State Management β”‚
β”‚ (Context API + React Query) β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Frontend Layers​

1. Application Shell​

The EZApp component provides:

  • Layout (header, sidebar, content)
  • Navigation menu
  • Authentication flow
  • Route protection
  • Global state providers
<EZApp
basePath='/'
extraPrivateRoutes={[
{
path: '/products',
element: withSuspense(ProductPage),
name: 'products',
}
]}
/>

2. Page Components​

Pages are lazy-loaded route components:

const ProductPage = lazy(() => import('@/pages/ProductPage'));

3. Shared Components​

Reusable UI components:

  • DataTable - Table with pagination, sorting, filtering
  • FormModal - Modal with form handling
  • FileUpload - File upload with progress
  • PermissionGuard - Component-level permission check

4. API Service Layer​

Axios-based HTTP client with interceptors:

// Request interceptor - add auth token
axios.interceptors.request.use((config) => {
const token = getToken();
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});

// Response interceptor - handle errors
axios.interceptors.response.use(
(response) => response,
(error) => {
if (error.response?.status === 401) {
// Token expired, redirect to login
window.location.href = '/login';
}
return Promise.reject(error);
}
);

5. State Management​

Two types of state:

Server State (React Query):

const { data, loading } = useRequest(() => apiGet('/users'));

Client State (React Context):

const { user, setUser } = useAuth();
const { settings } = useSettings();

Core Concepts​

1. Controller-Service Pattern​

Controllers handle HTTP concerns:

  • Parse request parameters
  • Validate input
  • Format responses
  • Handle HTTP errors

Services handle business logic:

  • Data processing
  • Business rules
  • Database operations
  • Integration with external systems

Benefits:

  • Clear separation of concerns
  • Easier testing (mock services)
  • Reusable business logic
  • Better maintainability

2. Resource-Based IDs​

All resources use UUID-based resource_id:

type Base struct {
ID uint `gorm:"primarykey" json:"-"`
ResourceID string `gorm:"uniqueIndex;size:36" json:"id"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
}

Why?

  • Prevents enumeration attacks
  • No information leakage about system size
  • Can be used across distributed systems
  • More secure than sequential IDs

Usage:

  • Internal: Use ID for database joins
  • External: Use ResourceID for APIs (exposed as id)

3. Soft Deletes​

All models support soft deletes via DeletedAt:

// Soft delete - sets DeletedAt
db.Delete(&user)

// Queries automatically exclude soft-deleted
db.Find(&users) // Won't include deleted users

// Include soft-deleted in query
db.Unscoped().Find(&users)

// Permanent delete
db.Unscoped().Delete(&user)

Benefits:

  • Data recovery possible
  • Audit trail maintained
  • Compliance with data retention policies

4. Audit Logging​

Automatic tracking of important operations:

err := svc.StartAudit(ctx, resourceID, func(auditLog *model.AuditLog) error {
// Perform operation
user, err := createUser(ctx, req)

// Configure audit log
auditLog.ResourceType = "user"
auditLog.ResourceID = user.ResourceID
auditLog.Action = "create"
auditLog.Details = map[string]interface{}{
"username": user.Username,
}

return err
})

Automatically Captured:

  • User ID and username
  • IP address and geolocation
  • User agent
  • Timestamp
  • Action and resource

5. RBAC with Policies​

Flexible authorization system:

User ─┬─ Role 1 ─┬─ Permission Group 1 ─┬─ Permission 1
β”‚ β”‚ └─ Permission 2
β”‚ └─ Permission Group 2 ─── Permission 3
└─ Role 2 ─── Permission Group 3 ─── Permission 4

Policy Documents (JSON):

{
"effect": "allow",
"actions": ["users:read", "users:write"],
"resources": ["users/*"],
"conditions": {
"ip_range": ["10.0.0.0/8"]
}
}

Permission Check:

// In controller
router.GET("/users",
middleware.RequirePermission("users:read"),
controller.ListUsers,
)

Built-in Modules​

EZ-Console provides several built-in modules:

1. User Management Module​

  • User CRUD operations
  • Password management
  • MFA configuration
  • Status management
  • Last login tracking

2. Authorization Module​

  • Role management
  • Permission definitions
  • Permission groups
  • Service accounts
  • Session management
  • OAuth2/OIDC integration
  • LDAP/AD integration

3. System Settings Module​

  • Password policies
  • MFA settings
  • Session timeout
  • Email/SMTP configuration
  • OAuth provider configuration

4. File Management Module​

  • File upload/download
  • Storage abstraction
  • Access control
  • File metadata

5. Audit Log Module​

  • Action logging
  • IP tracking and geolocation
  • User agent tracking
  • Searchable logs

6. Statistics Module​

  • User activity metrics
  • Login statistics
  • API usage tracking
  • System health

Extensibility​

Controllers and routes​

Register HTTP controllers from your application module (typically in init()):

func init() {
server.RegisterControllers(func(ctx context.Context, svc server.Service) server.Controller {
return NewProductController(svc)
})
}

Gin middleware​

The stock server applies Gin middleware inside the framework. To add your own, pass WithEngineOptions when building the command (same package as NewCommandServer):

import (
consoleserver "github.com/sven-victor/ez-console/server"
"github.com/gin-gonic/gin"
)

root := consoleserver.NewCommandServer(
"my-app", version, "My app",
consoleserver.WithEngineOptions(func(engine *gin.Engine) {
engine.Use(MyMiddleware())
}),
)

User and role change hooks​

The underlying authorization layer supports RegisterUserChangeHook and RegisterUserRoleChangeHook on server.Service. The default Run path constructs the service inside the framework, so registering these hooks from a plain main + NewCommandServer app may require following the patterns in the source or a custom bootstrap. See Hooks & Events.

Next Steps​

Now that you understand the architecture:

  1. Install EZ-Console on your machine
  2. Follow the Quick Start to build your first app
  3. Learn about Core Concepts in detail
  4. Explore Backend Development

Questions about the architecture? Ask on GitHub Discussions.