Как я написал пакет для быстрого создания и управления формами в Go
- суббота, 8 марта 2025 г. в 00:00:14
Привет, Хабровчане! Сегодня я хочу рассказать о своём небольшом проекте - пакете goform, который я написал для упрощения работы с HTML-формами в Go. Это не просто ещё один пакет, а результат моего опыта и желания сделать процесс работы с формами более удобным и эффективным. В этой статье я поделюсь историей создания, функциональностью пакета и тем, как он может быть полезен другим разработчикам.
Заранее хочу сказать что статья никоим образом не предназначена для пиара, понимаю что задачка фактически не очень сложная и возможно кто-то уже сделал что-то похожее для себя или организации в которой работает. А так же я не претендую на звание "мастера кода по всем видам кода".
Идея создания goform появилась во время работы над одним из моих пет-проектов. Я заметил, что постоянно приходится писать однотипный код для создания, рендеринга и валидации форм. Это было достаточно утомительно потому как в каждой форме приходилось руками подставлять значения. Мне захотелось создать инструмент, который бы автоматизировал эти процессы и позволил сосредоточиться на логике приложения, а не на рутинных задачах.
Так и родилась идея goform - пакета, который предоставляет удобный API для работы с HTML-формами в Go. Основная цель - сделать процесс создания и обработки форм максимально простым и гибким.
goform - это не просто обёртка для работы с формами. Это инструмент, который решает несколько ключевых задач:
Упрощение создания форм: Вместо того чтобы вручную прописывать каждое поле и его атрибуты, вы можете описать форму с помощью структуры Go, и GoForm сделает всё остальное.
Гибкость рендеринга: Пакет поддерживает рендеринг как в HTML, так и в JSON, что делает его универсальным для монолитных приложений и разделённых фронтенда и бэкенда.
Валидация данных: Пакет предоставляет встроенные правила валидации и позволяет добавлять кастомные.
Поддержка AJAX: Пакет автоматически определяет AJAX-запросы и возвращает данные в формате JSON.
Интеграция с фреймворками: goform легко интегрируется с фреймворком - Echo (просто потому что в своей практике написания веб-приложений на Go, я применяю его в 99% случаев).
Об остальном функционале сможете прочитать подробнее в документации пакета (ссылки предоставлю в конце статьи).
Для создания формы достаточно описать её структуру и вызвать функцию NewForm:
// Инициализируем структуру (поля будущей формы).
type RegistrationForm struct {
Username string `form:"username" validate:"required,min=3"`
Email string `form:"email" validate:"required,email"`
Password string `form:"password" validate:"required,min=6"`
Method string `form:"-"`
FormID string `form:"-"`
}
func main() {
http.HandleFunc("/someurl", func(w http.ResponseWriter, r *http.Request) {
model := &RegistrationForm{
Method: "SomeMethod",
FormID: "SomeFormID",
}
/*
model - структура, описывающая поля формы.
Method - HTTP-метод формы (например, "POST").
FormID - уникальный идентификатор формы.
*/
form := core.NewForm(model, model.Method, model.FormID)
// Остальной код
})
}
GoForm поддерживает рендеринг как в HTML, так и в JSON. Это особенно полезно, если фронтенд и бэкенд разделены:
response := form.ToResponse()
if form.RenderHTML {
// Рендеринг HTML
_ = tmpl.ExecuteTemplate(w, "имя_шаблона.html", response)
} else {
// Возврат JSON
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(response)
}
Пакет предоставляет встроенные правила валидации, такие как required, min, max, email, и позволяет добавлять кастомные:
form.AddCustomValidation("password", func(value string) error {
if len(value) < 6 {
return errors.New("password must be at least 6 characters long")
}
return nil
})
Пакет автоматически определяет AJAX-запросы и возвращает данные в формате JSON:
if isAjax(r) {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(form.ToResponse())
}
Пакет поддерживает генерацию и проверку CSRF-токенов для защиты от атак:
token, err := core.GenerateCSRFToken()
if err != nil {
http.Error(w, "Failed to generate CSRF token", http.StatusInternalServerError)
}
form.AddCSRFToken(token)
Разработка пакета не обошлась без сложностей. Вот основные проблемы, с которыми я столкнулся:
Гибкость рендеринга: Одна из главных задач - сделать пакет универсальным. Я хотел, чтобы он мог работать как с монолитными приложениями, так и с разделёнными фронтендом и бэкендом. Это потребовало реализации поддержки как HTML, так и JSON.
Валидация данных: Реализация гибкой системы валидации, которая поддерживает как встроенные правила, так и кастомные, оказалась нетривиальной задачей. Пришлось продумать, как эффективно обрабатывать ошибки и возвращать их пользователю.
Интеграция с фреймворком: Чтобы goform был полезен во-первых мне, а во-вторых максимальному числу разработчиков, я добавил поддержку фреймворка Echo. Это потребовало некоторого переосмысления, но результат того стоил.
goform - это не просто инструмент для работы с формами. Это решение, которое может сэкономить вам время и усилия. Вот несколько сценариев, где он может быть полезен:
Монолитные приложения: Если вы разрабатываете монолитное приложение, GoForm упростит создание и обработку форм.
Разделённые фронтенд и бэкенд: Пакет поддерживает рендеринг в JSON, что делает его идеальным для REST API.
Быстрое прототипирование: С goform вы можете быстро создавать и тестировать формы, не тратя время на рутинные задачи.
Вот пример простой формы регистрации:
package main
import (
"net/http"
"github.com/DBenyukh/goform/core"
"html/template"
"path/filepath"
"log"
)
type RegistrationForm struct {
Username string `form:"username" validate:"required,min=3"`
Email string `form:"email" validate:"required,email"`
Password string `form:"password" validate:"required,min=6"`
Method string `form:"-"`
FormID string `form:"-"`
}
var tmpl *template.Template
func init() {
projectDir, err := filepath.Abs(".")
if err != nil {
log.Fatalf("Error getting absolute project directory path: %v", err)
}
templateDir := filepath.Join(projectDir, "templates")
renderer, err := core.NewTemplateRenderer(templateDir, "")
if err != nil {
log.Fatalf("Failed to create template renderer: %v", err)
}
tmpl = renderer.Templates
}
func main() {
http.HandleFunc("/register", func(w http.ResponseWriter, r *http.Request) {
model := &RegistrationForm{
Method: "POST",
FormID: "register_form",
}
form := core.NewForm(model, model.Method, model.FormID)
form.RenderHTML = true
if r.Method == http.MethodGet {
// Рендеринг формы
response := form.ToResponse()
if form.RenderHTML {
// Рендеринг HTML
_ = tmpl.ExecuteTemplate(w, "имя_шаблона.html", response)
} else {
// Возврат JSON
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(response)
}
return
}
// Обработка POST-запроса
if err := form.Bind(r); err != nil {
http.Error(w, "Invalid form data", http.StatusBadRequest)
return
}
if err := form.Validate(model); err != nil {
// Возврат ошибок валидации
response := form.ToResponse()
if form.RenderHTML {
_ = tmpl.ExecuteTemplate(w, "имя_шаблона.html", response)
} else {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(response)
}
return
}
// Обработка успешной отправки формы
w.Write([]byte("User registered successfully!"))
})
http.ListenAndServe(":8080", nil)
}
И соответственно html-шаблон который использую:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<form method="{{ if eq .Method "GET" }}GET{{ else }}POST{{ end }}">
{{ if and (ne .Method "GET") (ne .Method "POST") }}
<input type="hidden" name="_method" value="{{ .Method }}">
{{ end }}
<input type="hidden" name="form_id" value="{{ .FormID }}">
{{ range .Fields }}
{{ if not .Hidden }}
<div>
<label>{{ .Name }}</label>
<input type="{{ .Type }}" name="{{ $.FormID }}_{{ .Name }}" value="{{ .Value }}">
{{ if .Error }}
<span style="color: red;">{{ .Error }}</span>
{{ end }}
</div>
{{ end }}
{{ end }}
<input type="hidden" name="{{ .FormID }}_csrf_token" value="{{ .CSRF }}">
<button type="submit">Submit</button>
</form>
</body>
</html>
goform - это результат моего желания сделать работу с формами в Go более удобной и эффективной. Пакет уже используется в моём проекте (над которым пока работаю), и я надеюсь, что он будет полезен и другим разработчикам. Если у вас есть идеи или предложения по улучшению, буду рад услышать их в комментариях / личке на хабре / ТГ (в ссылках). А так же буду рад всевозможной критике.
И конечно же ссылочки, куда без них:
Мой телеграм (если вдруг захотите просто пообщаться или предложить работу)
Спасибо за внимание! Удачи в ваших проектах!