gowww/app is a full featured HTTP framework for any web app.
It greatly increases productivity by providing helpers at all levels while maintaining best performance.
-
Install gowww/app:
go get github.com/gowww/app
-
Import it in your new app:
import "github.com/gowww/app"
There are methods for common HTTP methods:
app.Get("/", func(c *app.Context) {
// Write response for GET /
})
app.Post("/", func(c *app.Context) {
// Write response for POST /
})
app.Put("/", func(c *app.Context) {
// Write response for PUT /
})
app.Patch("/", func(c *app.Context) {
// Write response for PATCH /
})
app.Delete("/", func(c *app.Context) {
// Write response for DELETE /
})
A named parameter begins with :
and matches any value until the next /
in path.
To retrieve the value, ask Context.PathValue.
It will return the value as a string (empty if the parameter doesn't exist).
Example, with a parameter id
:
app.Get("/users/:id", func(c *app.Context) {
id := c.PathValue("id")
fmt.Fprintf(w, "Page of user #%s", id)
}))
If a parameter must match an exact pattern (digits only, for example), you can also set a regular expression constraint just after the parameter name and another :
:
app.Get(`/users/:id:^\d+$`, func(c *app.Context) {
id := c.PathValue("id")
fmt.Fprintf(w, "Page of user #%s", id)
}))
If you don't need to retrieve the parameter value but only use a regular expression, you can omit the parameter name.
A trailing slash behaves like a wildcard by matching the beginning of the request path and keeping the rest as a parameter value, under *
:
rt.Get("/files/", func(c *app.Context) {
filepath := c.PathValue("*")
fmt.Fprintf(w, "Get file %s", filepath)
}))
For more details, see gowww/router.
A routing group works like the top-level router but prefixes all subroute paths:
api := app.Group("/api")
{
v1 := api.Group("/v1")
{
v1.Get("/user", func(c *app.Context) { /* Write response for GET /api/v1/user */ })
v1.Get("/item", func(c *app.Context) { /* Write response for GET /api/v1/item */ })
}
v2 := api.Group("/v2")
{
v2.Get("/user", func(c *app.Context) { /* Write response for GET /api/v2/user */ })
v2.Get("/item", func(c *app.Context) { /* Write response for GET /api/v2/item */ })
}
}
You can set a custom "not found" handler with NotFound:
app.NotFound(func(c *app.Context) {
c.Status(http.StatusNotFound)
c.View("notFound")
})
The app is also recovered from panics so you can set a custom "serving error" handler (which is used only when the response is not already written) with Error and retrieve the recovered error value with Context.Error:
app.Error(func(c *app.Context) {
c.Status(http.StatusInternalServerError)
if c.Error() == ErrCannotOpenFile {
c.View("errorStorage")
return
}
c.View("error")
})
A Context is always used inside a Handler.
It contains the original request and response writer but provides all the necessary helpers to access them:
Use Context.Req to access the original request:
app.Get("/", func(c *app.Context) {
r := c.Req
})
Use Context.FormValue to access a value from URL or body.
You can also use Context.HasFormValue to check its existence:
app.Get("/", func(c *app.Context) {
if c.HasFormValue("id") {
id := c.FormValue("id")
}
})
Use Context.Res to access the original response writer:
app.Get("/", func(c *app.Context) {
w := c.Res
})
Use Context.Text or Context.Bytes to send a string:
app.Get("/", func(c *app.Context) {
c.Text("Hello")
c.Bytes([]byte("World"))
})
Use Context.JSON to send a JSON formatted response (if implemented by argument, JSON() interface{}
will be used):
app.Get(`/users/:id:^\d+$/files/`, func(c *app.Context) {
c.JSON(map[string]interface{}{
"userID": c.PathValue("id"),
"filepath": c.PathValue("*"),
})
})
Use Context.Status to set the response status code:
app.Get("/", func(c *app.Context) {
c.Status(http.StatusCreated)
})
Use Context.NotFound to send a "not found" response:
app.Get("/", func(c *app.Context) {
c.NotFound()
})
Use Context.Panic to log an error and send a "serving error" response:
app.Get("/", func(c *app.Context) {
c.Panic("database connection failed")
})
Use Context.Redirect to redirect the client:
app.Get("/old", func(c *app.Context) {
c.Redirect("/new", http.StatusMovedPermanently)
})
Use Context.Push to initiate an HTTP/2 server push:
app.Get("/", func(c *app.Context) {
c.Push("/static/main.css", nil)
})
You can use context values kept inside the context for future usage downstream (like views or subhandlers).
Use Context.Set to set a value:
app.Get("/", func(c *app.Context) {
c.Set("clientCountry", "UK")
})
Use Context.Get to retrieve a value:
app.Get("/", func(c *app.Context) {
clientCountry := c.Get("clientCountry")
})
Views are standard Go HTML templates and must be stored inside the views
directory.
They are automatically parsed during launch.
Use Context.View to send a view:
app.Get("/", func(c *app.Context) {
c.View("home")
})
Use a ViewData map to pass data to a view.
You can also use GlobalViewData to set data for all views:
app.GlobalViewData(app.ViewData{
"appName": "My app",
})
app.Get("/", func(c *app.Context) {
user := &User{
ID: 1,
Name: "John Doe",
}
c.View("home", app.ViewData{
"user": user,
})
})
In views/home.gohtml:
{{define "home"}}
<h1>Hello {{.user.Name}} ({{.c.Req.RemoteAddr}}) and welcome on {{.appName}}!</h1>
{{end}}
This data is always passed to the views, out of the box:
Data | Description |
---|---|
.c |
The current Context. |
.envProduction |
Tells if the app is run with the production flag. |
.errors |
See validation. |
Use GlobalViewFuncs to set functions for all views:
app.GlobalViewFuncs(app.ViewFuncs{
"pathescape": url.PathEscape,
})
app.Get("/posts/new", func(c *app.Context) {
c.View("postsNew")
})
In views/posts.gohtml:
{{define "postsNew"}}
<a href="/sign-in?return-to={{pathescape "/posts/new"}}">Sign in</a>
{{end}}
In addition to the functions provided by the standard template package, these function are also available out of the box:
Function | Description | Usage |
---|---|---|
asset |
Appends the file hash to the name of a static file from the static directory. |
{{asset "videos/loop.mp4"}} |
googlefonts |
Sets HTML tag for Google Fonts stylesheet and given font(s). | {{googlefonts "Open+Sans:400,700|Spectral"}} |
nl2br |
Converts \n to HTML <br> . |
{{nl2br "line one\nline two"}} |
safehtml |
Prevents string to be escaped. Be careful. | {{safehtml "<strong>word</strong>"}} |
script |
Sets HTML tag for a script from the static/script directory. |
{{script "main.js"}} |
style |
Sets HTML tag for a stylesheet from the static/style directory. |
{{style "main.css"}} |
Validation is handled by gowww/check.
Firstly, make a Checker with rules for keys:
userChecker := check.Checker{
"email": {check.Required, check.Email, check.Unique(db, "users", "email", "?")},
"phone": {check.Phone},
"picture": {check.MaxFileSize(5000000), check.Image},
}
The rules order is significant so for example, it's smarter to check the format of a value before its uniqueness, avoiding some useless database requests.
Use Context.Check to check the request against a checker:
errs := c.Check(userChecker)
Use Errors.Empty or Errors.NotEmpty to know if there are errors and handle them like you want.
You can also translate error messages with Context.TErrors:
if errs.NotEmpty() {
c.Status(http.StatusBadRequest)
c.View(view, app.ViewData{"errors": errs})
return
}
But usually, when a check fails, you only want to send a response with error messages.
Here comes the BadRequest shortcut which receives a checker and a view name.
If you don't provide a view name (empty string), the response will be a JSON errors map.
If the check fails, it sets the status to "400 Bad Request", sends the response (view or JSON) and returns true
, allowing you to exit from the handler:
app.Post("/join", func(c *app.Context) {
if c.BadRequest(userChecker, "join") {
return
}
// Handle request confidently
})
In views, you can retrieve the TranslatedErrors map under key errors
which is never nil
in view data:
<input type="email" name="email" value="{{.email}}">
{{if .errors.Has "email"}}
<div class="error">{{.errors.First "email"}}</div>
{{end}}
Internationalization is handled by gowww/i18n.
Firstly, make your translations map (string to string, for each language):
locales := i18n.Locales{
language.English: {
"hello": "Hello!",
},
language.French: {
"hello": "Bonjour !",
},
}
Use Localize to register it and set the default locale (used as a fallback):
app.Localize(locales, language.English)
Methods Context.T, Context.Tn, Context.THTML and Context.TnHTML are now operational.
As the Context is always part of the view data, you can use these methods in views:
<h1>{{.c.T "hello"}}</h1>
Static files must be stored inside the static
directory.
They are automatically accessible from the /static/
path prefix.
Call Run at the end of your main function:
app.Run()
By default, your app will listen and serve on :8080
.
But you can change this address by using flag -a
when running your app:
./myapp -a :1234
Custom middlewares can be used if they are compatible with standard interface net/http.Handler.
They can be set for:
-
The entire app:
app.Run(hand1, hand2, hand3)
-
A group:
api := app.Group("/api", hand1, hand2, hand3)
-
A single route:
api := app.Get("/", func(c *app.Context) { // Write response for GET / }, hand1, hand2, hand3)
First handler wraps the second and so on.