Excel Exporter
The Excel Exporter Service provides functionality to export data from your application to Excel files with automatic formatting, styling, and upload management.
Overview
The Excel Exporter enables:
- Query Export: Export SQL query results directly to Excel
- Custom Data Sources: Implement custom data providers
- Automatic Styling: Apply formatting, colors, and fonts
- Sheet Configuration: Multiple sheets, frozen headers, auto-filter
- File Management: Automatic upload and versioning
- Streaming Export: Memory-efficient handling of large datasets
Basic Usage
Importing the Service
import "github.com/iota-uz/iota-sdk/modules/core/services"
type MyController struct {
excelService services.ExcelExportService
}
Export from SQL Query
func (c *MyController) ExportUsers(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
upload, err := c.excelService.ExportFromQuery(
ctx,
"SELECT id, first_name, last_name, email, created_at FROM users WHERE active = true",
"active_users", // filename without .xlsx
true, // include headers
)
if err != nil {
http.Error(w, "Export failed", http.StatusInternalServerError)
return
}
// Redirect to download
http.Redirect(w, r, upload.URL().String(), http.StatusSeeOther)
}
Advanced Usage
Export with Options
// Configure export behavior
exportOpts := &excel.ExportOptions{
IncludeHeaders: true,
AutoFilter: true, // Enable filter buttons
FreezeHeader: true, // Freeze first row
DateFormat: "2006-01-02",
MaxRows: 10000, // Limit to 10k rows
SheetName: "Users",
}
upload, err := c.excelService.ExportFromQueryWithOptions(
ctx,
"SELECT * FROM users",
"users_export",
exportOpts,
nil,
)
Custom Styling
styleOpts := &excel.StyleOptions{
HeaderStyle: &excel.CellStyle{
Font: &excel.FontStyle{
Bold: true,
Size: 12,
Color: "#FFFFFF",
},
Fill: &excel.FillStyle{
Type: "pattern",
Pattern: 1,
Color: "#4CAF50",
},
Alignment: &excel.AlignmentStyle{
Horizontal: "center",
Vertical: "center",
},
},
AlternateRow: true,
AltRowColor: "#F0F0F0",
}
upload, err := c.excelService.ExportFromQueryWithOptions(
ctx,
"SELECT * FROM orders",
"orders_report",
exportOpts,
styleOpts,
startDate, endDate,
)
Export from Custom Data Source
Implement the DataSource interface for custom data:
type ReportDataSource struct {
reportService *ReportService
reportType string
}
func (r *ReportDataSource) GetHeaders() []string {
return []string{"Month", "Revenue", "Expenses", "Profit", "Growth %"}
}
func (r *ReportDataSource) GetSheetName() string {
return "Monthly Report"
}
func (r *ReportDataSource) GetRows(ctx context.Context) (func() ([]interface{}, error), error) {
// Fetch data
data, err := r.reportService.GetMonthlyReport(ctx, r.reportType)
if err != nil {
return nil, err
}
index := 0
return func() ([]interface{}, error) {
if index >= len(data) {
return nil, nil // EOF
}
row := data[index]
index++
return []interface{}{
row.Month,
row.Revenue,
row.Expenses,
row.Profit,
row.GrowthPercent,
}, nil
}, nil
}
// Use custom data source
upload, err := c.excelService.ExportFromDataSource(
ctx,
&ReportDataSource{
reportService: c.reportService,
reportType: "monthly",
},
"monthly_report",
exportOpts,
)
Multiple Sheets
// Export multiple sheets
sheets := []excel.SheetData{
{
Name: "Summary",
DataSource: summaryDataSource,
},
{
Name: "Details",
DataSource: detailsDataSource,
},
{
Name: "Metadata",
DataSource: metadataDataSource,
},
}
upload, err := c.excelService.ExportMultiSheet(
ctx,
sheets,
"comprehensive_report",
)
Field Types and Formatting
Numeric Formatting
// Currency
styleOpts.ColumnFormats = map[string]string{
"amount": "#,##0.00",
"total": "[$$-409]#,##0.00;-[$$-409]#,##0.00",
}
// Percentage
styleOpts.ColumnFormats["growth_percent"] = "0.00%"
// Integer
styleOpts.ColumnFormats["quantity"] = "0"
Date/Time Formatting
// Date
styleOpts.ColumnFormats["order_date"] = "YYYY-MM-DD"
// DateTime
styleOpts.ColumnFormats["created_at"] = "YYYY-MM-DD HH:MM:SS"
// Time
styleOpts.ColumnFormats["delivery_time"] = "HH:MM:SS"
Conditional Formatting
styleOpts.ConditionalFormats = []excel.ConditionalFormat{
{
Range: "D:D", // Amount column
Type: "colorScale",
Min: excel.ColorScaleValue{
Value: 0,
Color: "#FF0000", // Red for low amounts
},
Mid: excel.ColorScaleValue{
Value: 50,
Color: "#FFFF00", // Yellow for medium
},
Max: excel.ColorScaleValue{
Value: 100,
Color: "#00FF00", // Green for high amounts
},
},
}
Column Configuration
Column Widths
styleOpts.ColumnWidths = map[string]int{
"A": 20, // First column 20 chars wide
"B": 30,
"C": 15,
}
Column Formatting
styleOpts.ColumnStyles = map[string]*excel.CellStyle{
"C": {
Font: &excel.FontStyle{Color: "#0000FF"},
},
"D": {
Fill: &excel.FillStyle{Color: "#FFFFCC"},
},
}
Performance Optimization
Streaming Large Datasets
// Process large exports in chunks
func (c *MyController) ExportLargeDataset(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
// Stream rows to avoid loading all in memory
dataSource := &StreamingDataSource{
query: "SELECT * FROM large_table",
batchSize: 1000,
}
upload, err := c.excelService.ExportFromDataSource(
ctx,
dataSource,
"large_export",
&excel.ExportOptions{
MaxRows: 100000, // Limit for safety
},
)
}
Progress Tracking
// Track export progress
upload, err := c.excelService.ExportWithProgress(
ctx,
dataSource,
"report",
func(processed, total int64) {
// Update progress
log.Printf("Exported %d/%d rows", processed, total)
},
)
Error Handling
Common Errors
upload, err := c.excelService.ExportFromQuery(ctx, query, filename, true)
if err != nil {
switch err {
case excel.ErrMaxRowsExceeded:
// Handle too many rows
http.Error(w, "Too many rows to export", http.StatusBadRequest)
case excel.ErrInvalidQuery:
// Handle SQL errors
http.Error(w, "Invalid query", http.StatusBadRequest)
case excel.ErrUploadFailed:
// Handle upload failure
http.Error(w, "Failed to save file", http.StatusInternalServerError)
default:
http.Error(w, err.Error(), http.StatusInternalServerError)
}
return
}
Controller Integration
As a Standalone Endpoint
func (c *UserController) ExportUsers(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
logger := composables.UseLogger(ctx)
logger.Info("Starting user export")
upload, err := c.excelService.ExportFromQuery(
ctx,
`SELECT u.id, u.first_name, u.last_name, u.email,
COUNT(o.id) as order_count
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE u.active = true
GROUP BY u.id`,
"users_with_orders",
true,
)
if err != nil {
logger.WithField("error", err.Error()).Error("Export failed")
http.Error(w, "Export failed", http.StatusInternalServerError)
return
}
logger.Info("Export completed successfully")
// Option 1: Redirect to download
http.Redirect(w, r, upload.URL().String(), http.StatusSeeOther)
// Option 2: Return URL as JSON
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]string{
"downloadUrl": upload.URL().String(),
"filename": upload.Name(),
})
}
With Filters
func (c *OrderController) ExportOrders(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
// Get filter parameters
startDate := r.FormValue("start_date")
endDate := r.FormValue("end_date")
status := r.FormValue("status")
// Build dynamic query
query := `SELECT id, order_number, customer_name, total, status, created_at
FROM orders WHERE 1=1`
params := []interface{}{}
if startDate != "" {
query += " AND created_at >= $" + fmt.Sprint(len(params)+1)
params = append(params, startDate)
}
if endDate != "" {
query += " AND created_at <= $" + fmt.Sprint(len(params)+1)
params = append(params, endDate)
}
if status != "" {
query += " AND status = $" + fmt.Sprint(len(params)+1)
params = append(params, status)
}
upload, err := c.excelService.ExportFromQueryWithParams(
ctx,
query,
"orders",
true,
params...,
)
// Return download...
}
Testing Exports
func TestExportUsers(t *testing.T) {
suite := controllertest.New(t, userModule)
// Test export endpoint
response := suite.GET("/users/export").
Expect(t).
Status(302)
// Verify redirect to file URL
location := response.Header("Location")
if location == "" {
t.Error("Expected redirect location")
}
// Verify file was created
if !strings.HasSuffix(location, ".xlsx") {
t.Error("Expected Excel file download")
}
}
Configuration
Environment Variables
# Maximum file size
EXCEL_MAX_ROWS=100000
EXCEL_MAX_COLUMNS=100
# Temporary storage location
EXCEL_TEMP_DIR=/tmp/excel_exports
# Auto-cleanup duration
EXCEL_CLEANUP_AGE_HOURS=24
Best Practices
- Limit Row Count: Always set
MaxRowsto prevent memory exhaustionexportOpts.MaxRows = 50000 - Include Headers: Always use
IncludeHeaders: truefor usabilityexportOpts.IncludeHeaders = true - Use Freeze for Headers: Freeze header rows for better usability
exportOpts.FreezeHeader = true - Format Numbers Properly: Use appropriate formatting for numeric columns
styleOpts.ColumnFormats["amount"] = "#,##0.00" - Add Validation: Validate queries and parameters
if len(query) > 10000 { return errors.New("query too large") } - Log Operations: Log all export operations for audit trails
logger.WithField("filename", filename).Info("Excel export completed")
Limitations and Solutions
| Limitation | Solution |
|---|---|
| Large datasets | Use streaming and row limits |
| Complex queries | Pre-aggregate or use custom data source |
| Column constraints | Use dynamic field mapping |
| Memory usage | Process in batches |
For more information, see the Advanced Features Overview or the Excel Exporter Service documentation.