README
¶
GoForm
GoForm
— это пакет для работы с HTML-формами в Go. Он предоставляет удобный API для создания, рендеринга, валидации и обработки форм. Пакет поддерживает рендеринг форм в HTML и JSON, кастомную валидацию, обработку AJAX-запросов и многое другое.
Оглавление
- Установка
- Быстрый старт
- Основные функции
- Расширенные возможности
- Пример HTML-шаблона
- Примеры
- Лицензия
Установка
Для установки пакета выполните команду:
go get github.com/DBenyukh/goform
Быстрый старт
Вот пример создания и рендеринга простой формы:
package main
import (
"net/http"
"github.com/DBenyukh/goform/core"
)
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)
}
Основные функции
Создание формы
Для создания формы используйте функцию NewForm
:
form := core.NewForm(model, method, formID)
model
— структура, описывающая поля формы.
method
— HTTP-метод формы (например, "POST").
formID
— уникальный идентификатор формы.
Рендеринг формы
Для рендеринга формы используйте метод ToResponse
:
response := form.ToResponse()
Этот метод возвращает данные формы в зависимости от флага RenderHTML:
- Если
RenderHTML = true
, возвращается структура FormResponse для рендеринга HTML. - Если
RenderHTML = false
, возвращается JSON.
Пример использования:
if form.RenderHTML {
_ = tmpl.ExecuteTemplate(w, "имя_шаблона.html", form.ToResponse())
} else {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(response)
}
Валидация формы
Для валидации данных формы используйте метод Validate
:
if err := form.Validate(model); err != nil {
// Обработка ошибок валидации
}
Обработка AJAX-запросов
Библиотека автоматически определяет AJAX-запросы и возвращает данные в формате JSON:
if isAjax(r) {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(form.ToResponse())
}
Расширенные возможности
Кастомная валидация
Вы можете добавлять кастомные правила валидации:
form.AddCustomValidation("password", func(value string) error {
if len(value) < 6 {
return errors.New("password must be at least 6 characters long")
}
return nil
})
Поддержка нескольких форм
Библиотека поддерживает несколько форм на одной странице. Убедитесь, что у каждой формы уникальный formID
.
Рендеринг HTML и JSON
Вы можете управлять форматом вывода с помощью флага RenderHTML
:
form.RenderHTML = false // Возвращает JSON
form.RenderHTML = true // Возвращает HTML
Интеграция с Echo
Пакет поддерживает интеграцию с фреймворком Echo.
Для этого используйте рендерер шаблонов, предоставляемый goform
.
Пример интеграции:
package main
import (
"github.com/labstack/echo/v4"
"github.com/DBenyukh/goform/core"
)
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() {
e := echo.New()
// Инициализация рендерера шаблонов
templateDir := "templates"
renderer, err := core.NewTemplateRenderer(templateDir, "default.html")
if err != nil {
e.Logger.Fatal("Failed to create template renderer:", err)
}
e.Renderer = renderer
e.GET("/register", func(c echo.Context) error {
model := &RegistrationForm{
Method: "POST",
FormID: "register_form",
}
form := core.NewForm(model, model.Method, model.FormID)
form.RenderHTML = true
// Рендеринг формы
return c.Render(http.StatusOK, "default.html", form.ToResponse())
})
e.Logger.Fatal(e.Start(":8080"))
}
Кастомные сообщения об ошибках
Вы можете указывать кастомные сообщения об ошибках валидации с помощью тега validate_msg
в структуре формы. Например:
type RegistrationForm struct {
Username string `form:"username" validate:"required,min=3" validate_msg:"Username must be at least 3 characters"`
Email string `form:"email" validate:"required,email" validate_msg:"Please provide a valid email address"`
Password string `form:"password" validate:"required" validate_msg:"Password is required"`
Method string `form:"-"`
FormID string `form:"-"`
}
Эти сообщения будут использоваться при валидации и отображаться в форме, если данные не соответствуют правилам.
Скрытые поля
Вы можете создавать скрытые поля, которые не отображаются в форме, но передаются на сервер. Для этого установите поле Hidden
в true
:
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:"-"`
HiddenField string `form:"hidden_field" hidden:"true"` // Скрытое поле
}
В HTML-шаблоне такие поля не будут отображаться, но их значения будут переданы на сервер.
CSRF-токены
Пакет поддерживает генерацию и проверку CSRF-токенов для защиты от атак. Для этого используйте метод AddCSRFToken
:
token, err := core.GenerateCSRFToken()
if err != nil {
http.Error(w, "Failed to generate CSRF token", http.StatusInternalServerError)
}
form.AddCSRFToken(token)
CSRF-токен автоматически добавляется в форму и проверяется при обработке POST-запросов.
Пример использования в обработчике:
if r.Method == http.MethodPost {
csrfTokenFromForm := r.FormValue(model.FormID + "_csrf_token")
csrfTokenFromCookie, err := r.Cookie("csrf_token")
if err != nil {
http.Error(w, "CSRF token missing in cookies", http.StatusForbidden)
return
}
if csrfTokenFromCookie.Value != csrfTokenFromForm {
http.Error(w, "Invalid CSRF token", http.StatusForbidden)
return
}
}
Пример HTML-шаблона
Пример шаблона default.html
для рендеринга формы:
<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>
Примеры
Пример 1: Простая форма регистрации
См. раздел Быстрый старт.
Пример 2: Кастомная валидация
form.AddCustomValidation("username", func(value string) error {
if strings.Contains(value, " ") {
return errors.New("username cannot contain spaces")
}
return nil
})
Пример 3: Разделённый фронтенд и бэкенд с net/http
Бэкенд (Go)
package main
import (
"encoding/json"
"log"
"net/http"
"github.com/DBenyukh/goform/core"
)
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() {
// В HandleFunc первым аргументом указываем роут до вашего api регистрации
http.HandleFunc("/api/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 = false // Возвращаем JSON
if r.Method == http.MethodGet {
// Возвращаем данные формы в формате JSON
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(form.ToResponse())
return
}
if r.Method == http.MethodPost {
// Привязка данных из запроса к форме
if err := form.Bind(r); err != nil {
http.Error(w, "Invalid form data", http.StatusBadRequest)
return
}
// Валидация данных
if err := form.Validate(model); err != nil {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusBadRequest)
json.NewEncoder(w).Encode(form.ToResponse())
return
}
// Обработка успешной отправки формы
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]string{"message": "User registered successfully!"})
return
}
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
})
log.Fatal(http.ListenAndServe(":8080", nil))
}
Фронтенд (JavaScript + HTML)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Registration Form</title>
</head>
<body>
<div id="form-container"></div>
<script>
// Загрузка формы с бэкенда
let apiRegister = 'http://localhost:8080/api/register' // указываем ссылку на ваш api
fetch(apiRegister)
.then(response => response.json())
.then(data => {
const formContainer = document.getElementById('form-container');
let formHTML = `<form method="POST" action=`apiRegister`>`; // используем ссылку на api в action
data.fields.forEach(field => {
formHTML += `
<div>
<label>${field.name}</label>
<input type="${field.type}" name="${field.name}" value="${field.value}">
${field.error ? `<span style="color: red;">${field.error}</span>` : ''}
</div>`;
});
formHTML += `
<input type="hidden" name="csrf_token" value="${data.csrf}">
<button type="submit">Submit</button>
</form>`;
formContainer.innerHTML = formHTML;
});
// Обработка отправки формы
document.addEventListener('submit', async (e) => {
e.preventDefault();
const formData = new FormData(e.target);
const response = await fetch(apiRegister, {
method: 'POST',
body: formData,
});
if (response.ok) {
const result = await response.json();
alert(result.message);
} else {
const errors = await response.json();
alert(`Validation errors: ${JSON.stringify(errors)}`);
}
});
</script>
</body>
</html>
Пример 4: Разделённый фронтенд и бэкенд с Echo
Бэкенд (Go + Echo)
package main
import (
"github.com/labstack/echo/v4"
"github.com/DBenyukh/goform/core"
)
type RegistrationForm struct {
Username string `form:"username" validate:"required,min=3" validate_msg:"Username must be at least 3 characters"`
Email string `form:"email" validate:"required,email" validate_msg:"Please provide a valid email address"`
Password string `form:"password" validate:"required" validate_msg:"Password is required"`
Method string `form:"-"`
FormID string `form:"-"`
}
func main() {
e := echo.New()
// В e.GET первым аргументом указываем роут до вашего api регистрации
e.GET("/api/register", func(c echo.Context) error {
model := &RegistrationForm{
Method: "POST",
FormID: "register_form",
}
form := core.NewForm(model, model.Method, model.FormID)
form.RenderHTML = false // Возвращаем JSON
return c.JSON(http.StatusOK, form.ToResponse())
})
// В e.POST первым аргументом указываем роут до вашего api регистрации
e.POST("/api/register", func(c echo.Context) error {
model := &RegistrationForm{
Method: "POST",
FormID: "register_form",
}
form := core.NewForm(model, model.Method, model.FormID)
form.RenderHTML = false
// Привязка данных из запроса к форме
if err := form.Bind(c.Request()); err != nil {
return c.JSON(http.StatusBadRequest, map[string]string{"error": "Invalid form data"})
}
// Валидация данных
if err := form.Validate(model); err != nil {
return c.JSON(http.StatusBadRequest, form.ToResponse())
}
// Обработка успешной отправки формы
return c.JSON(http.StatusOK, map[string]string{"message": "User registered successfully!"})
})
e.Logger.Fatal(e.Start(":8080"))
}
Фронтенд (JavaScript + HTML)
Фронтенд остаётся таким же, как в предыдущем примере, так как API бэкенда не изменилось.
Лицензия
Этот проект распространяется под лицензией MIT. Подробнее см. в файле LICENSE.