Preparing your codebase

Gohandlers enforces the best development habits on handler definitions in terms of readability and maintainability. You need to write one struct for each handler which will represent the request and response data of that handler. This way maintaining consistency across all endpoints throughout the development process for fields, types, validation and serialization becomes joy, and a soft codebase compliance is enforced at each make build.

Package structure

Let’s say you have resource “user” and actions create, read, update, delete on it. The rules and best practices combined needs you to define a central struct for holding the common dependencies of handlers:

type User struct {
  JustTheCommonDeps  any
  TheOtherLayer      any
  OrTheDatabase      any
  OrARateLimiter     any
  YouWillSetThoseTho any
  NotOurBusiness     any
}

Then, you are expected to define your handlers with User type being the receiver:

func (u *User) Create(w http.ResponseWriter, r *http.Request)
func (u *User) Read(w http.ResponseWriter, r *http.Request)
func (u *User) Update(w http.ResponseWriter, r *http.Request)
func (u *User) Delete(w http.ResponseWriter, r *http.Request)

You probably will put each into different files. But make sure they match the http.HandlerFunc signature. Gohandlers CLI recognizes handlers by the input and output parameter list. They either match at the character basis, or not. The receivers are okay to be empty, so function handlers are allowed. In fact, when you generate the helpers file on such directory with Go files that includes the combination of function and method handlers, you’ll notice there will be multiple listers as they are generated separately to match its receiver to handler’s receiver. More on that later in the Listers page.

File structure

Gohandlers expects one handler per file and its optional request and response binding struct declarations. The names for binding types should take the handler name as prefix and only add Request or Response to their ends. Gohandler will check if the handler body mentions the binding types. Complying with both rules is required for making Gohandlers consider the handler and its bindings for the code generation. Notice this file below contains 3 declarations. The handler name is Create and its bindings CreateRequest and CreateResponse. Verb handler names and resource receivers are as short as it gets. This naming pattern is highly encouraged to be considered when naming handlers.

type CreateRequest struct {
  Name types.PetName `json:"name"`
  Tag  types.PetTag  `json:"tag"`
}

type CreateResponse struct {
  ID string `json:"id"`
}

func (p *Pets) Create(w http.ResponseWriter, r *http.Request) {
  _ := &CreateRequest{}
  _ := &CreateResponse{}
}

Just like above, even for under construction handlers, the body should mention the binding types prior to the code generation. Or the code generation will skip binding types. Notice the request binding field types are custom. You will need to implement your types that supports serialization depending on the context (route, query or form body) and implements its custom field validation method. The former is not required when json is used but even then field validators will be required. So, using core types for request bindings may generate code failing in compilation due to the missing methods.

Tagging fields

Tags tell Gohandlers which part of HTTP request/response the builders/parsers should look for reading or writing values. So you need to tag binding type fields with supported tags.

type ListRequest struct {
  Limit types.ListLimit `query:"limit"` // optional
}

Each field should include one of the following:

Tag Request Response Position
route A NA Header
query A NA Header
form A NA Body
json A A Body

Implement field serialization methods

When a type is used as a field type inside a binding type, it might need to implement a couple of interfaces. For types used as a field type for fields with route tag, the type needs to implement Routier interface below. This would allow request and response builders and parsers to perform their operations without cutting your hands off from implementing custom serialization methods per-type.

type Routier interface {
  FromRoute(v string) error
  ToRoute() (string, error)
}

Types used as a field type for query parameters via query tag need to implement Querier interface below. Compared to others, To method needs to return 3 parameter. In addition to the standard encoded value and encoding error it is expected to return a middle one. Which represents the existence of value. If all query parameters returns false, the request builder will skip adding the ? query section to the URL.

type Querier interface {
  FromQuery(v string) error
  ToQuery() (string, bool, error)
}

Types used as a field type for fields with form tags, methods of Formier interface is expected to serialize according to the x-www-form-urlencoded. Opposed to the json bodies, marshaling and unmarshaling form bodies with Gohandlers provided methods doesn’t involve reflect. But also they don’t support nested data structures.

type Formier interface {
  FromForm(v string) error
  ToForm() (string, error)
}

Implement field validators

To use the request validators generated by the validate command, users are required to implent the FieldValidator interface on every type used as a field type to a request response type:

type FieldValidator interface {
  Validate() any
}

For example, if there is a UserId type used as field type to a request binding, then the user needs to implement:

func (u UserId) Validate() any

Validate methods are forbidden to perform resource intensive operations like database accesses for their purpose, as they are solely exist for being called by request validators. Which are meant to be called for filtering invalid requests as cheaply and early as possible inside handlers, prior to resourceful operations. This only allows formal inspection of values. Thus, they are mostly expected to perform length, range or pattern checks.

Field validators return issues instead of errors for both their technical and semantical meanings. That is because Go error are meant to stay private to the server, often utilized for only internal purposes. At the other hand, validators are expected to return ready-to-serialization values for sending back to the client. This can be as basic as a string value that is worded as an explanation to the user; or an error code that will be processed by the frontend app for localization. For fields with collection types, the return value can be a slice or map issues.

Beware that return values are constrained by the constraints of serializer that will be used inside the handler for request validation issues. For example, using JSON encoder to serialize request validator response will cause the output to miss any error value returned by field validators.

Once the request validators generated and called by your app, the IDE warnings (via gopls) for types with missing field validators will make the user notice problems at an instant, far before the app goes production.

Naming handlers

Gohandlers can infer the HTTP method of an handler just by looking to the verb at the beginning of handler name and/or the request binding body parameters. Any handler name starts

Method Prefix
GET Get, Visit
HEAD Head
POST Post, Upload, Create
PUT Put, Replace
PATCH Patch, Update
DELETE Delete, Remove
CONNECT Connect
OPTIONS Options
TRACE Trace

Annotate handlers

To set the HTTP method, endpoint path or both of an handler it is possible to use function doc-comments. To overwrite the method inferred from request binding body and handler name prefix with the selection of yours.

// POST /account
func (a *Api) CreateAccount(w http.ResponseWriter, r *http.Request) {
  bq := &CreateAccountRequest{}

If an handler’s specified and inferred methods conflict then the code genration will provide a warning or an error. Such as in the below example the specified method is requiring a body, but the request binding type doesn’t contain a field with json or form tags.

type CreateAuthorizationForEventRequest struct {
  EventId basics.String `route:"eid"`
}

// POST /event/{eid}/authorization
func (a *Api) CreateAuthorizationForEvent(w http.ResponseWriter, r *http.Request)

You can also limit or disable Gohandlers on the per-handler basis. Gohandlers can ignore selected handlers completely or only include them in the listers. The latter is suggested for endpoints where handling the request parsing, and/or validation involve special logic. Such as in multipart or stream request. To only list a handler, use the list directive like below. This would cause Gohandlers to skip implementing request builder, parser, validator and response builder with writer on the binding types for that handler.

// gh:list
func (p Pets) UploadPhoto(w http.ResponseWriter, r *http.Request)

To instruct Gohandlers to completely ignore a handler you need to annotate the handler as such below.

// gh:ignore
func (p Pets) CreateStream(w http.ResponseWriter, r *http.Request)