Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/blakewilliams/fernet
Simple Go web framework that uses generics for safer data sharing between middleware, router, and handlers
https://github.com/blakewilliams/fernet
Last synced: 2 months ago
JSON representation
Simple Go web framework that uses generics for safer data sharing between middleware, router, and handlers
- Host: GitHub
- URL: https://github.com/blakewilliams/fernet
- Owner: BlakeWilliams
- License: mit
- Created: 2023-08-26T16:29:17.000Z (over 1 year ago)
- Default Branch: main
- Last Pushed: 2024-08-05T00:32:37.000Z (5 months ago)
- Last Synced: 2024-08-05T18:37:00.139Z (5 months ago)
- Language: Go
- Size: 78.1 KB
- Stars: 3
- Watchers: 2
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# Fernet
A simple Go framework for building web applications. Fernet uses generics to
provide convenient and type-safe APIs for your Handlers and Middleware.## Getting Started
```go
package mainimport (
"fmt"
"net/http""github.com/blakewilliams/fernet"
)// RequestContext is used to store data that is shared between middleware and
// handlers. Methods can be defined on this type to provide application specific
// functionality like rendering.
type RequestContext struct {
currentUser *User
fernet.RequestContext
}// Implement a basic render string helper function.
func (r *RequestContext) RenderString(code int, s string) {
r.ResponseWriter().WriteHeader(code)
r.ResponseWriter().Write([]byte(s))
}func main() {
app := fernet.New(func(r *fernet.RequestContext) *RequestContext {
return &RequestContext{RequestContext: r}
})// Use is used to add fernet based middleware to the application.
app.Use(func(ctx context.Context, r *RequestContext, next fernet.Handler[RequestContext]) {
// Do something before the request is handled.
next(ctx, r)
// Do something after the request is handled.
})// Fernet routing uses : to define named parameters in the path. Wildcards are also supported via *.
app.Get("/hello/:name", func(ctx context.Context, r *RequestContext) {
r.WriteString(http.StatusOK, fmt.Sprintf("Hello %s", rc.Params()["name"]))
})// Handle 404s by defining a catch-all route.
app.Get("*", func(ctx context.Context, r *RequestContext) {
r.WriteString(http.StatusNotFound, "Not Found")
}app.ListenAndServe(":3200")
}
```## Controllers
Controllers allow you to group related handlers together using a struct that is
initialized each request and passed to the handler as an additional argument.
This allows you to extract data from the request and pass it to the handler
after common behavior like fetching records and authorization. e.g.```go
// Define a type that implements FromRequest and can store the team record.
type TeamData struct { Team *Team }// Implement the FromRequest method. If it returns false, the handler will not
// be called. If it returns true, the request will be processed as normal.
func (td *TeamData) FromRequest(ctx context.Context, rc *AppRequestContext) error {
td.Team = rc.TeamRepository.Find(ctx, rc.Params["team_id"])
// Handle missing data
if td.Team == nil {
rc.Render404()
return false
}// Handle authorization
if rc.TeamRepository.IsMember(ctx, rc.CurrentUser, td.Team) {
rc.Render403()
return false
}return true
}// Define a handler that accepts the TeamData type.
func Show(ctx context.Context, rc *AppRequestContext, td *TeamData) {
rc.RenderJSON(http.StatusOK, td.Team)
}// Setup the router
router := fernet.New(func(r *fernet.RequestContext) *AppRequestContext {
return &AppRequestContext{RequestContext: r}
})teamsController := fernet.Controller(router, *TeamData{})
teamsController.Get("/teams/:team_id", Show)adminTeamController := teamsRouter.Namespace("/admin")
adminTeamController.Use(func(ctx context.Context, rc *AppRequestContext, next fernet.Handler[AppRequestContext]) {
if rc.CurrentUser.Role != "admin" {
rc.Render403()
return
}next(ctx, rc)
})
adminTeamController.Get("/teams/:team_id/settings", Update)
```## Groups and Namespaces
Groups are used to group routes together and apply middleware common only to those groups and subgroups. Namespaces are exactly like groups, but accept a prefix string that is prepended to all routes within the namespace.
```go
type RequestContext struct {
currentUser *User
fernet.RequestContext
}func (r *RequestContext) RenderString(code int, s string) {
r.ResponseWriter().WriteHeader(code)
r.ResponseWriter().Write([]byte(s))
}app := fernet.New(func(r *fernet.RequestContext) *RequestContext {
return &RequestContext{RequestContext: r}
})authGroup := router.Group()
authGroup.Use(func(ctx context.Context, r *RequestContext, next fernet.Handler[RequestContext]) {
if r.AppData.currentUser == nil {
r.RenderString(http.StatusUnauthorized, "Unauthorized")
return
}next(ctx, r)
})adminGroup := authGroup.Namespace("/admin")
adminGroup.Use(func(ctx context.Context, r *RequestContext, next fernet.Handler[RequestContext]) {
if r.AppData.currentUser == nil || r.AppData.currentUser.Role != "admin" {
r.RenderString(http.StatusUnauthorized, "Unauthorized")
return
}next(ctx, r)
})
```## Middleware
Fernet provides a few middleware functions out of the box. Import the
`github.com/blakewilliams/fernet/middleware` package to use them.- `middleware.ErrorHandler` - rescues panics and calls a `Handler[T]` to handle
the error.
- `middleware.Logger` - logs requests and responses using slog.## Metal
Fernet provides `Metal` handlers, which operate against net/http `Request` and
`ResponseWriter` types. These handlers are useful for integrating with existing
middleware and libraries or modifying the request/response before it is passed
to the `RequestContext` handler.- `metal.MethodRewrite` - Rewrites the HTTP method based on the value of the `_method` form value.