https://github.com/goioc/web
Web Framework for Go, based on goioc/di
https://github.com/goioc/web
go golang golang-library rest-api web web-framework
Last synced: 3 months ago
JSON representation
Web Framework for Go, based on goioc/di
- Host: GitHub
- URL: https://github.com/goioc/web
- Owner: goioc
- License: mit
- Created: 2020-06-22T21:10:29.000Z (almost 6 years ago)
- Default Branch: master
- Last Pushed: 2024-11-25T09:12:48.000Z (over 1 year ago)
- Last Synced: 2024-11-25T10:29:11.657Z (over 1 year ago)
- Topics: go, golang, golang-library, rest-api, web, web-framework
- Language: Go
- Homepage: https://pkg.go.dev/github.com/goioc/web/?tab=doc
- Size: 75.2 KB
- Stars: 4
- Watchers: 1
- Forks: 1
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Contributing: CONTRIBUTING.md
- Funding: .github/FUNDING.yml
- License: LICENSE
- Code of conduct: CODE_OF_CONDUCT.md
Awesome Lists containing this project
README
# goioc/web: Web Framework for Go, based on goioc/di
[](https://github.com/goioc)

[](https://pkg.go.dev/github.com/goioc/web/?tab=doc)
[](https://www.codefactor.io/repository/github/goioc/web)
[](https://goreportcard.com/report/github.com/goioc/web)
[](https://codecov.io/gh/goioc/web)
[](https://sonarcloud.io/dashboard?id=goioc_web)
[](https://deepsource.io/gh/goioc/web/?ref=repository-badge)
## How is this framework different from others?
1. First of all, `goioc/web` is working using Dependency Injection and is based on [goioc/di](https://github.com/goioc/di), which is the IoC Container.
2. Secondly - and this is the most exciting part - web-endpoints in `goioc/web` can have (almost) arbitrary signature!
No more `func(w http.ResponseWriter, r *http.Request)` handlers, if your endpoint receives a `string` and produces a binary stream, just declare it as is:
```go
...
func (e *endpoint) Hello(name string) io.Reader {
return bytes.NewBufferString("Hello, " + name + "!")
}
...
```
Cool, huh? 🤠Of course, you can still directly use `http.ResponseWriter` and `*http.Request`, if you like.
## Basic concepts
The main entity in `goioc/web` is the Endpoint, which is represented by the interface of the same name. Here's the example implementation:
```go
type endpoint struct {
}
func (e endpoint) HandlerFuncName() string {
return "Hello"
}
func (e *endpoint) Hello(name string) io.Reader {
return bytes.NewBufferString("Hello, " + name + "!")
}
```
`Endpoint` interface has one method that returns the name of the method that will be used as an endpoint.
In order for `goioc/web` to pick up this endpoint, it should be registered in the DI Container:
```go
_, _ = di.RegisterBean("endpoint", reflect.TypeOf((*endpoint)(nil)))
```
Then the container should be initialized (please, refer to the [goioc/di](https://github.com/goioc/di) documentation for more details):
```go
_ = di.InitializeContainer()
```
Finally, the web-server can be started, either using the built-in function:
```go
_ = web.ListenAndServe(":8080")
```
... or using returned `Router`
```go
router, _ := web.CreateRouter()
_ = http.ListenAndServe(":8080", router)
```
## Routing
So, how does the framework know where to bind this endpoint to?
For the routing functionality `goioc/web` leverages [gorilla/mux](https://github.com/gorilla/mux) library.
Don't worry: you don't have to cope with this library directly: `goioc/web` provides a set of convenient wrappers around it.
The wrappers are implemented as tags in the endpoint-structure. Let's slightly update our previous example:
```go
...
type endpoint struct {
method interface{} `web.methods:"GET"`
path interface{} `web.path:"/hello"`
}
...
```
Now our endpoint is bound to a `GET` requests at the `/hello` path. Yes, it's that simple! 🙂
### `goioc/web` tags
| **Tag** | **Value** | **Example** |
|---------------|-------------------------------------------|-------------------------------------------------------|
| `web.methods` | List of HTTP-methods. | `web.methods:"POST,PATCH"` |
| `web.path` | URL sub-path. Can contain path variables. | `web.path:"/articles/{category}/{id:[0-9]+}"` |
| `web.queries` | Key-value paris of the URL query part. | `web.queries:"foo,bar,id,{id:[0-9]+}"` |
| `web.headers` | Key-value paris of the request headers. | `web.headers:"Content-Type,application/octet-stream"` |
| `web.matcher` | ID of the bean of type `*mux.MatcherFunc`.| `web.matcher:"matcher"` |
## In and Out types
As was mentioned above, with `goioc/web` you get a lot of freedom in terms of defining the signature of your endpoint's method.
Just look at these examples:
```go
...
func (e *endpoint) Error() (int, string) {
return 505, "Something bad happened :("
}
...
```
```go
...
func (e *endpoint) KeyValue(ctx context.Context) string {
return ctx.Value(di.BeanKey("key")).(string)
}
...
```
```go
...
func (e *endpoint) Hello(pathParams map[string]string) (http.Header, int) {
return map[string][]string{
"Content-Type": {"application/octet-stream"},
}, []byte("Hello, " + pathParams["name"] + "!")
}
...
```
### Supported argument types
- `http.ResponseWriter`
- `*http.Request`
- `context.Context`
- `http.Header`
- `io.Reader`
- `io.ReadCloser`
- `[]byte`
- `string`
- `map[string]string`
- `url.Values`
- `struct` implementing `encoding.BinaryUnmarshaler` or `encoding.TextUnmarshaler`
- `interface{}` (`GoiocSerializer` bean is used to deserialize such arguments)
### Supported return types
- `http.Header` (response headers, must be first return argument, if used)
- `int` (status code, must be first argument after response headers, if used)
- `io.Reader`
- `io.ReadCloser`
- `[]byte`
- `string`
- `struct` implementing `encoding.BinaryMarshaler` or `encoding.TextMarshaler`
- `interface{}` (`GoiocSerializer` bean is used to serialize such returned object)
### Templates
`goioc/web` supports templates!
**todo.html**
```html
{{.PageTitle}}
- {{.Title}}
- {{.Title}}
{{range .Todos}}
{{if .Done}}
{{else}}
{{end}}
{{end}}
```
**endpoint.go**
```go
type todo struct {
Title string
Done bool
}
type todoPageData struct {
PageTitle string
Todos []todo
}
type todoEndpoint struct {
method interface{} `web.methods:"GET"`
path interface{} `web.path:"/todo"`
}
func (e todoEndpoint) HandlerFuncName() string {
return "TodoList"
}
func (e *todoEndpoint) TodoList() (template.Template, interface{}) {
tmpl := template.Must(template.ParseFiles("todo.html"))
return *tmpl, todoPageData{
PageTitle: "My TODO list",
Todos: []todo{
{Title: "Task 1", Done: false},
{Title: "Task 2", Done: true},
{Title: "Task 3", Done: true},
},
}
}
```
**Note** that in case of using templates, the next returned object after `template.Template` must be the actual structure that will be used to fill in the template 💡
## Custom matchers
If functionality of `web.methods`, `web.path`, `web.queries` and `web.headers` is not enough for you, you can use custom matcher,
based on Gorilla's `mux.MatcherFunc`:
```go
...
_, _ = di.RegisterBeanFactory("matcher", di.Singleton, func(context.Context) (interface{}, error) {
matcherFunc := mux.MatcherFunc(func(request *http.Request, match *mux.RouteMatch) bool {
return strings.HasSuffix(request.URL.Path, "bar")
})
return &matcherFunc, nil
})
...
type endpoint struct {
method interface{} `web.methods:"GET"`
path interface{} `web.path:"/endpoint/{key}/{*?}"`
matcher interface{} `web.matcher:"matcher"`
}
func (e endpoint) HandlerFuncName() string {
return "Match"
}
func (e *endpoint) Match() string {
return "It's a match! :)"
}
...
```
```bash
$ curl localhost:8080/endpoint/foo/bar
It's a match! :)
```
## Middleware
Of course, custom middleware is also supported by the framework:
```go
web.Use(func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
next.ServeHTTP(w, r.WithContext(context.WithValue(r.Context(), di.BeanKey("key"), "value")))
})
})
```