Quick Start - Build Your First App
This guide will walk you through creating your first EZ-Console application in about 10 minutes. You'll build a simple product management API with a React frontend.
Prerequisites
Before you start, install the following on your machine.
Required software
1. Go (1.20 or higher)
Check:
go version
# Should output: go version go1.20.x or higher
Install: golang.org/dl (Windows/macOS installers; Linux tarball or distro packages). On macOS you can use brew install go.
Optional — GOPATH on older setups:
export GOPATH=$HOME/go
export PATH=$PATH:$GOPATH/bin
2. Node.js (18 or higher)
Check:
node --version
# Should output: v18.x.x or higher
Install: nodejs.org or your OS package manager / NodeSource on Linux.
3. pnpm
Check:
pnpm --version
Install:
npm install -g pnpm
Other install options: pnpm.io/installation.
4. Git
Check: git --version
Install: git-scm.com or your OS package manager (brew install git, apt install git, etc.).
Optional but recommended
- Make — for generated project
Makefileworkflows (make --version; on Linux,build-essentialoften includes it). - Docker — for deployment-style setups (docker.com).
Encryption key (when you run the server)
EZ-Console requires --global.encrypt-key (or equivalent config / env) to encrypt sensitive data in the database.
The value for --global.encrypt-key must be 8, 16, 24, or 32 bytes (character length for ASCII). This key encrypts sensitive data at rest. Do not change it after initial production use or existing encrypted values can become unreadable.
Examples used in this guide (development only):
go run main.go --global.encrypt-key=test-key-16bytes
For flag precedence and related options, see CLI commands.
What We'll Build
We're going to create a simple product management system with:
- Backend: A RESTful API for managing products
- Frontend: A React page to display and interact with products
- Full Integration: Frontend calling backend APIs
Initialize with ez-console init (recommended)
The framework repository ships a Cobra CLI that can scaffold a minimal backend + web tree (root Makefile, backend/, web/ with Vite, React, and the ez-console npm package). Build or install the ez-console binary from github.com/sven-victor/ez-console, then run init from a shell:
ez-console init product-manager \
--name product-manager \
--go-module github.com/yourusername/product-manager/backend
cd product-manager
| Argument / flag | Meaning |
|---|---|
product-manager | Project root directory (created if missing) |
--name | Service name passed to NewCommandServer; also used for the default SQLite file name in generated backend/config.yaml |
--go-module | Module path for backend/go.mod (must match what you use in Go imports) |
Optional flags include --app-version (default 0.1.0), --ez-console-version (pinned github.com/sven-victor/ez-console module version), and --description.
Typical follow-up: from the project root, make build builds web + backend; run the backend from backend/ (go run main.go with your encrypt key), and the web dev server from web/ (pnpm install then pnpm run start — the generated Vite config proxies /api and /console to http://localhost:8080). To develop against a local clone of the framework, add a replace github.com/sven-victor/ez-console => /path/to/ez-console in backend/go.mod and run go mod tidy.
If you use init for this tutorial: skip Step 1 below. In Step 2, keep your module path consistent with --go-module instead of github.com/yourusername/product-manager. In Step 3, merge or add the files shown into the generated web/ tree (pnpm install first) instead of creating an empty web/ from scratch.
Full flag list and generate stubs: CLI commands (aligned with the upstream usages/18-code-generation-cli.md guide in the framework repo).
Step 1: Create Project Structure
ez-console initIf you already scaffolded with ez-console init, you have backend/ and web/ — go to Step 2.
Create your project directory:
mkdir product-manager
cd product-manager
mkdir -p backend/controller backend/service
mkdir -p web/src/pages
Your initial structure:
product-manager/
├── backend/
│ ├── controller/
│ └── service/
└── web/
└── src/
└── pages/
Step 2: Initialize Backend
ez-console initYou already have backend/go.mod, backend/main.go, and sample packages. Add backend/controller/product.go as below (or alongside the samples). Ensure main.go blank-imports your controller package if it does not already. Use the same module path as --go-module. Skip subsections that duplicate what the scaffold created (go mod init, base main.go).
Create Go Module
cd backend
go mod init github.com/yourusername/product-manager
Install Dependencies
go get github.com/gin-gonic/gin
go get github.com/sven-victor/ez-console@latest
Create Main Entry Point
Create backend/main.go:
package main
import (
_ "github.com/yourusername/product-manager/controller"
consoleserver "github.com/sven-victor/ez-console/server"
)
const VERSION = "1.0.0"
var rootCmd = consoleserver.NewCommandServer(
"product-manager",
VERSION,
"Product Management System",
)
func main() {
rootCmd.Execute()
}
Create Product Controller
Create backend/controller/product.go:
package controller
import (
"context"
"net/http"
"time"
"github.com/gin-gonic/gin"
"github.com/sven-victor/ez-console/server"
"github.com/sven-victor/ez-console/pkg/util"
)
// Product represents a product
type Product struct {
ID string `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
Price float64 `json:"price"`
Stock int `json:"stock"`
CreatedAt time.Time `json:"created_at"`
}
// ProductController handles product endpoints
type ProductController struct {
svc server.Service
}
// In-memory storage (for demo purposes)
var products = []Product{
{
ID: "1",
Name: "Laptop",
Description: "High-performance laptop",
Price: 1299.99,
Stock: 50,
CreatedAt: time.Now(),
},
{
ID: "2",
Name: "Mouse",
Description: "Wireless mouse",
Price: 29.99,
Stock: 200,
CreatedAt: time.Now(),
},
{
ID: "3",
Name: "Keyboard",
Description: "Mechanical keyboard",
Price: 99.99,
Stock: 100,
CreatedAt: time.Now(),
},
}
// ListProducts returns all products
func (c *ProductController) ListProducts(ctx *gin.Context) {
util.RespondWithSuccess(ctx, http.StatusOK, products)
}
// GetProduct returns a single product
func (c *ProductController) GetProduct(ctx *gin.Context) {
id := ctx.Param("id")
for _, product := range products {
if product.ID == id {
util.RespondWithSuccess(ctx, http.StatusOK, product)
return
}
}
util.RespondWithError(ctx, util.NewErrorMessage("E4041", "Product not found"))
}
// CreateProduct creates a new product
func (c *ProductController) CreateProduct(ctx *gin.Context) {
var req Product
if err := ctx.ShouldBindJSON(&req); err != nil {
util.RespondWithError(ctx, util.NewErrorMessage("E4001", "Invalid request", err))
return
}
// Generate ID and set created time
req.ID = util.GenerateUUID()
req.CreatedAt = time.Now()
products = append(products, req)
util.RespondWithSuccess(ctx, http.StatusCreated, req)
}
// RegisterRoutes registers all routes for this controller
func (c *ProductController) RegisterRoutes(ctx context.Context, router *gin.RouterGroup) {
products := router.Group("/products")
{
products.GET("", c.ListProducts)
products.GET("/:id", c.GetProduct)
products.POST("", c.CreateProduct)
}
}
// NewProductController creates a new product controller
func NewProductController(svc server.Service) *ProductController {
return &ProductController{svc: svc}
}
// Register the controller with the framework
func init() {
server.RegisterControllers(func(ctx context.Context, svc server.Service) server.Controller {
return NewProductController(svc)
})
}
Run the Backend
go run main.go --global.encrypt-key=test-key-16bytes
The server will start on http://localhost:8080.
Test the API
# List all products
curl http://localhost:8080/api/products
# Get a single product
curl http://localhost:8080/api/products/1
# Create a product
curl -X POST http://localhost:8080/api/products \
-H "Content-Type: application/json" \
-d '{
"name": "Monitor",
"description": "4K Monitor",
"price": 399.99,
"stock": 30
}'
All successful responses follow this format:
{
"code": "0",
"data": { /* your data here */ }
}
Step 3: Initialize Frontend
ez-console initThe scaffold already includes package.json, vite.config.ts, and TypeScript config with /api (and /console) proxying to http://localhost:8080. Run pnpm install in web/, then add or merge src/App.tsx, src/pages/ProductPage.tsx, src/main.tsx, and index.html as shown below (adjust paths if the generated layout differs). You may keep the generated pnpm run start script instead of pnpm dev if that is what the template provides.
Create package.json
In a new terminal, navigate to the web directory:
cd web
Create web/package.json:
{
"name": "product-manager-web",
"version": "0.1.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview"
},
"dependencies": {
"ahooks": "^3.9.6",
"antd": "^5.14.1",
"ez-console": "github:sven-victor/ez-console.git#v1.9.1&path:web",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@types/node": "^20.11.19",
"@types/react": "^18.2.55",
"@types/react-dom": "^18.2.19",
"@vitejs/plugin-react": "^4.2.1",
"less": "^4.2.2",
"typescript": "^5.3.3",
"vite": "^5.1.0"
}
}
Install Dependencies
pnpm install
Create Vite Configuration
Create web/vite.config.ts:
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import path from 'path'
export default defineConfig({
plugins: [react()],
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
},
},
server: {
port: 5173,
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true,
},
},
},
})
Create TypeScript Configuration
Create web/tsconfig.json:
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
"strict": true,
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
}
Create web/tsconfig.node.json:
{
"compilerOptions": {
"composite": true,
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true
},
"include": ["vite.config.ts"]
}
Create App Component
Create web/src/App.tsx:
import { ConfigProvider } from 'antd';
import { EZApp, i18n, withSuspense } from 'ez-console';
import { lazy } from 'react';
const ProductPage = lazy(() => import('@/pages/ProductPage'));
// Add translations
i18n.addResource('en', 'translation', 'menu.products', 'Products');
i18n.addResource('en', 'translation', 'products.title', 'Product Management');
i18n.addResource('en', 'translation', 'products.name', 'Name');
i18n.addResource('en', 'translation', 'products.description', 'Description');
i18n.addResource('en', 'translation', 'products.price', 'Price');
i18n.addResource('en', 'translation', 'products.stock', 'Stock');
export default function App() {
return (
<ConfigProvider
theme={{
token: {
colorPrimary: '#1890ff',
}
}}
>
<EZApp
basePath='/'
extraPrivateRoutes={[
{
path: '/products',
element: withSuspense(ProductPage),
name: 'products',
index: true,
}
]}
/>
</ConfigProvider>
);
}
Create Product Page
Create web/src/pages/ProductPage.tsx:
import { Card, Table, Tag, Typography, Space, Button, Modal, Form, Input, InputNumber, message } from 'antd';
import { PlusOutlined } from '@ant-design/icons';
import { useTranslation, apiGet, apiPost } from 'ez-console';
import { useRequest } from 'ahooks';
import { useState } from 'react';
const { Title } = Typography;
const { Column } = Table;
interface Product {
id: string;
name: string;
description: string;
price: number;
stock: number;
created_at: string;
}
export const ProductPage: React.FC = () => {
const { t } = useTranslation();
const [isModalOpen, setIsModalOpen] = useState(false);
const [form] = Form.useForm();
// Fetch products
const { data, loading, refresh } = useRequest(
() => apiGet<Product[]>('/products')
);
// Create product
const { run: createProduct, loading: creating } = useRequest(
(values) => apiPost('/products', values),
{
manual: true,
onSuccess: () => {
message.success('Product created successfully');
setIsModalOpen(false);
form.resetFields();
refresh();
},
onError: (error) => {
message.error('Failed to create product');
},
}
);
const handleCreate = () => {
form.validateFields().then((values) => {
createProduct(values);
});
};
return (
<Card>
<Space direction="vertical" size="large" style={{ width: '100%' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<Title level={2}>{t('products.title')}</Title>
<Button
type="primary"
icon={<PlusOutlined />}
onClick={() => setIsModalOpen(true)}
>
Add Product
</Button>
</div>
<Table
dataSource={data}
loading={loading}
rowKey="id"
pagination={{ pageSize: 10 }}
>
<Column title={t('products.name')} dataIndex="name" key="name" />
<Column
title={t('products.description')}
dataIndex="description"
key="description"
/>
<Column
title={t('products.price')}
dataIndex="price"
key="price"
render={(price) => `$${price.toFixed(2)}`}
/>
<Column
title={t('products.stock')}
dataIndex="stock"
key="stock"
render={(stock) => (
<Tag color={stock > 50 ? 'green' : stock > 0 ? 'orange' : 'red'}>
{stock} units
</Tag>
)}
/>
</Table>
</Space>
<Modal
title="Create Product"
open={isModalOpen}
onOk={handleCreate}
onCancel={() => setIsModalOpen(false)}
confirmLoading={creating}
>
<Form form={form} layout="vertical">
<Form.Item
name="name"
label="Product Name"
rules={[{ required: true, message: 'Please input product name' }]}
>
<Input placeholder="Enter product name" />
</Form.Item>
<Form.Item
name="description"
label="Description"
rules={[{ required: true, message: 'Please input description' }]}
>
<Input.TextArea rows={3} placeholder="Enter description" />
</Form.Item>
<Form.Item
name="price"
label="Price"
rules={[{ required: true, message: 'Please input price' }]}
>
<InputNumber
min={0}
precision={2}
style={{ width: '100%' }}
prefix="$"
/>
</Form.Item>
<Form.Item
name="stock"
label="Stock"
rules={[{ required: true, message: 'Please input stock' }]}
>
<InputNumber min={0} style={{ width: '100%' }} />
</Form.Item>
</Form>
</Modal>
</Card>
);
};
export default ProductPage;
Create Entry Point
Create web/src/main.tsx:
import ReactDOM from 'react-dom/client';
import App from './App.tsx';
import 'ez-console/lib/style.css';
ReactDOM.createRoot(document.getElementById('root')!).render(
<App />
);
Create HTML Template
Create web/index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Product Manager</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
Run the Frontend
pnpm dev
The frontend will start on http://localhost:5173.
Step 4: Test Your Application
-
Open your browser to
http://localhost:5173 -
Login with default credentials:
- Username:
admin - Password:
Admin@123
First LoginYou'll be prompted to change the password on first login.
- Username:
-
Navigate to the Products page from the menu
-
View products - You should see the 3 default products
-
Create a product - Click "Add Product" and fill in the form
-
Verify the product appears in the list
Project Structure
Your complete project should look like this:
product-manager/
├── backend/
│ ├── controller/
│ │ └── product.go
│ ├── main.go
│ ├── go.mod
│ └── go.sum
└── web/
├── src/
│ ├── pages/
│ │ └── ProductPage.tsx
│ ├── App.tsx
│ └── main.tsx
├── index.html
├── package.json
├── tsconfig.json
├── tsconfig.node.json
└── vite.config.ts
What You've Learned
Congratulations! You've just built a full-stack application with EZ-Console. You've learned:
Backend
- ✅ How to create a Go module
- ✅ How to create controllers
- ✅ How to define API endpoints
- ✅ How to handle requests and responses
- ✅ How to use the framework's utility functions
Frontend
- ✅ How to set up a Vite project
- ✅ How to use the EZApp shell
- ✅ How to create pages
- ✅ How to call backend APIs
- ✅ How to use Ant Design components
Integration
- ✅ How frontend and backend work together
- ✅ How to handle authentication
- ✅ How to proxy API requests in development
Next Steps
Now that you've built your first application, here's what to explore next:
Improve the Backend
- Add a Service Layer - Move business logic from controller to service
- Use a Database - Replace in-memory storage with GORM
- Add Validation - Implement request validation
- Add Authentication - Protect endpoints with JWT
- Add Pagination - Implement list pagination
Learn more in Backend Development.
Enhance the Frontend
- Add Edit/Delete - Implement CRUD operations
- Add Filters - Implement search and filtering
- Add Pagination - Handle large datasets
- Improve UI - Add more components
- Add Forms - Create complex forms with validation
Learn more in Frontend Development.
Production Ready
- Error Handling - Implement proper error handling
- Logging - Add structured logging
- Testing - Write unit and integration tests
- Configuration - Use config files and environment variables
- Deployment - Deploy to production
Learn more in Deployment.
Operate via the admin console
- Sign in at
/consoleand change the default admin password - Users & roles — User Management, Role & Permission Management
- Monitoring — Audit Log Viewer, Statistics & Reports
Troubleshooting
Backend won't start
- Verify Go is installed:
go version - Check if port 8080 is available
- Ensure encryption key is correct length
Frontend won't build
- Delete
node_modulesand reinstall:rm -rf node_modules && pnpm install - Verify Node.js version:
node --version - Check for TypeScript errors
API calls fail
- Ensure backend is running on port 8080
- Check Vite proxy configuration
- Verify API paths are correct
Can't login
- Use default credentials:
admin/Admin@123 - Check browser console for errors
- Verify JWT token is being sent
Admin console
- Open
http://localhost:8080/console(or your deployed/consoleURL) for built-in administration - See Admin Console Overview and User Management for operators
Additional Resources
- Full Example: Check the
/demodirectory in the repository - API Documentation:
http://localhost:8080/swagger - Component Library: Ant Design 5 documentation
- React Hooks: ahooks documentation
Built your first app? Great! Continue to Core Concepts to deepen your understanding, or jump to Backend Development to build more complex APIs.