Quick Start - Build Your First App
This guide will walk you through creating your first EZ-Console application in 10 minutes. You'll build a simple product management API with a React frontend.
Make sure you've completed the Installation guide before starting.
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
Step 1: Create Project Structure
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
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
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:
admin123
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/admin123 - 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.