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 :1234Custom 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.