Как я написал пакет для быстрого создания и управления формами в Go

от автора

Привет, Хабровчане! Сегодня я хочу рассказать о своём небольшом проекте — пакете goform, который я написал для упрощения работы с HTML-формами в Go. Это не просто ещё один пакет, а результат моего опыта и желания сделать процесс работы с формами более удобным и эффективным. В этой статье я поделюсь историей создания, функциональностью пакета и тем, как он может быть полезен другим разработчикам.

Заранее хочу сказать что статья никоим образом не предназначена для пиара, понимаю что задачка фактически не очень сложная и возможно кто-то уже сделал что-то похожее для себя или организации в которой работает. А так же я не претендую на звание «мастера кода по всем видам кода».


Как всё начиналось

Идея создания goform появилась во время работы над одним из моих пет-проектов. Я заметил, что постоянно приходится писать однотипный код для создания, рендеринга и валидации форм. Это было достаточно утомительно потому как в каждой форме приходилось руками подставлять значения. Мне захотелось создать инструмент, который бы автоматизировал эти процессы и позволил сосредоточиться на логике приложения, а не на рутинных задачах.

Так и родилась идея goform — пакета, который предоставляет удобный API для работы с HTML-формами в Go. Основная цель — сделать процесс создания и обработки форм максимально простым и гибким.

Какой у goform функционал на данный момент?

goform — это не просто обёртка для работы с формами. Это инструмент, который решает несколько ключевых задач:

  1. Упрощение создания форм: Вместо того чтобы вручную прописывать каждое поле и его атрибуты, вы можете описать форму с помощью структуры Go, и GoForm сделает всё остальное.

  2. Гибкость рендеринга: Пакет поддерживает рендеринг как в HTML, так и в JSON, что делает его универсальным для монолитных приложений и разделённых фронтенда и бэкенда.

  3. Валидация данных: Пакет предоставляет встроенные правила валидации и позволяет добавлять кастомные.

  4. Поддержка AJAX: Пакет автоматически определяет AJAX-запросы и возвращает данные в формате JSON.

  5. Интеграция с фреймворками: goform легко интегрируется с фреймворком — Echo (просто потому что в своей практике написания веб-приложений на Go, я применяю его в 99% случаев).

Об остальном функционале сможете прочитать подробнее в документации пакета (ссылки предоставлю в конце статьи).


Основные функции пакета

1. Создание формы

Для создания формы достаточно описать её структуру и вызвать функцию 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)          // Остальной код     }) }

2. Рендеринг формы

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) }

3. Валидация данных

Пакет предоставляет встроенные правила валидации, такие как requiredminmaxemail, и позволяет добавлять кастомные:

Скрытый текст
form.AddCustomValidation("password", func(value string) error {     if len(value) < 6 {         return errors.New("password must be at least 6 characters long")     }     return nil })

4. Обработка AJAX-запросов

Пакет автоматически определяет AJAX-запросы и возвращает данные в формате JSON:

Скрытый текст
if isAjax(r) {     w.Header().Set("Content-Type", "application/json")     json.NewEncoder(w).Encode(form.ToResponse()) }

5. Поддержка CSRF-токенов

Пакет поддерживает генерацию и проверку CSRF-токенов для защиты от атак:

Скрытый текст
token, err := core.GenerateCSRFToken() if err != nil {     http.Error(w, "Failed to generate CSRF token", http.StatusInternalServerError) } form.AddCSRFToken(token)


С чем я столкнулся во время разработки?

Разработка пакета не обошлась без сложностей. Вот основные проблемы, с которыми я столкнулся:

  1. Гибкость рендеринга: Одна из главных задач — сделать пакет универсальным. Я хотел, чтобы он мог работать как с монолитными приложениями, так и с разделёнными фронтендом и бэкендом. Это потребовало реализации поддержки как HTML, так и JSON.

  2. Валидация данных: Реализация гибкой системы валидации, которая поддерживает как встроенные правила, так и кастомные, оказалась нетривиальной задачей. Пришлось продумать, как эффективно обрабатывать ошибки и возвращать их пользователю.

  3. Интеграция с фреймворком: Чтобы goform был полезен во-первых мне, а во-вторых максимальному числу разработчиков, я добавил поддержку фреймворка Echo. Это потребовало некоторого переосмысления, но результат того стоил.

Как goform может быть полезен?

goform — это не просто инструмент для работы с формами. Это решение, которое может сэкономить вам время и усилия. Вот несколько сценариев, где он может быть полезен:

  1. Монолитные приложения: Если вы разрабатываете монолитное приложение, GoForm упростит создание и обработку форм.

  2. Разделённые фронтенд и бэкенд: Пакет поддерживает рендеринг в JSON, что делает его идеальным для REST API.

  3. Быстрое прототипирование: С 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 более удобной и эффективной. Пакет уже используется в моём проекте (над которым пока работаю), и я надеюсь, что он будет полезен и другим разработчикам. Если у вас есть идеи или предложения по улучшению, буду рад услышать их в комментариях / личке на хабре / ТГ (в ссылках). А так же буду рад всевозможной критике.

И конечно же ссылочки, куда без них:

Пакет goform

Фреймворк Echo

Мой телеграм (если вдруг захотите просто пообщаться или предложить работу)

Спасибо за внимание! Удачи в ваших проектах!


ссылка на оригинал статьи https://habr.com/ru/articles/888728/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *