Routing
Routing is Go's net/http.ServeMux. ezz registers METHOD /pattern entries on a mux it owns, so pattern syntax, precedence, and conflict rules are the standard library's.
Register a route
Each HTTP method has a matching function on the app. They all take a pattern and an http.Handler, and return a *RouteBuilder you can chain metadata onto.
app := ezz.New()
app.GET("/users", listUsers)
app.POST("/users", createUser)
app.PUT("/users/{id}", replaceUser)
app.PATCH("/users/{id}", updateUser)
app.DELETE("/users/{id}", deleteUser)
app.HEAD("/health", healthHead)
app.OPTIONS("/users", usersOptions)
// any other method
app.Handle("TRACE", "/debug", traceHandler)
The handler can be an ezz.H typed handler, an http.HandlerFunc, or anything else implementing http.Handler:
app.GET("/legacy", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("plain net/http still works"))
})).Public()
Typed handlers additionally expose their request and response types, which is what the OpenAPI generator reads. A plain http.Handler still routes and still respects auth rules, it just has no schema to document.
Match path parameters
Patterns use {name} for a single segment and {name...} for a trailing wildcard.
app.GET("/users/{id}", handler) // /users/42
app.GET("/files/{path...}", handler) // /files/a/b/c.txt
app.GET("/orgs/{org}/repos/{repo}", handler)
Read them through the request type, which is the usual case:
type GetUserRequest struct {
ID string `path:"id" validate:"required"`
}
Or read them from the context when you have no request struct:
app.GET("/users/{id}", ezz.HFunc(func(ctx *ezz.Context) (*User, error) {
return lookup(ctx.Param("id"))
}))
Group related routes
app.Group(prefix) returns a *RouteGroup that shares a path prefix, and optionally middleware and an auth rule, with every route registered on it.
api := app.Group("/api/v1")
api.GET("", listRoot) // /api/v1
api.GET("/users", listUsers) // /api/v1/users
Groups nest, and a child copies the parent's middleware and auth rule at the moment it is created:
api := app.Group("/api/v1").Use(middleware.Gzip())
admin := api.Group("/admin").Auth(ezz.Role("admin"))
admin.GET("/stats", stats) // /api/v1/admin/stats, gzipped, admin only
Trailing slashes on the prefix are trimmed, so Group("/api/") and Group("/api") behave the same.
Use and Auth on a group before registering routes on it or creating children from it.Override auth per route
Precedence runs from broad to narrow: the app default, then the group rule, then the route rule.
app := ezz.New().Auth(provider) // default: any valid token
app.GET("/health", health).Public() // no auth
app.GET("/admin", panel).Auth(ezz.Role("admin")) // admin only
app.GET("/me", profile) // inherits the default
To make routes public by default and opt into auth instead, pass nil:
app := ezz.New().Auth(provider).DefaultAuth(nil)
app.GET("/open", handler) // public
app.GET("/closed", handler).Auth(ezz.Authenticated())
See Auth rules for the full set of rules.
Attach metadata for the docs
Every registration returns a *RouteBuilder:
app.GET("/users/{id}", getUser).
Tags("Users").
Description("Returns a single user by ID.").
Summary("Get user").
OperationID("getUser").
Response(404, "User not found").
Deprecated()
| Method | Effect |
|---|---|
Auth(rule) | Sets the auth rule for this route |
Public() | Shorthand for Auth(ezz.Public()) |
Tags(...) | OpenAPI tags, also used to group operations in the docs UI |
Description(text) | OpenAPI operation description |
Summary(text) | Stored on the route |
OperationID(id) | Stored on the route |
Deprecated() | Stored on the route |
Response(status, description, example...) | Stored on the route |
Start serving
// the convenience path
log.Fatal(app.Listen(":8080"))
// or drive the server yourself, since App is an http.Handler
srv := &http.Server{
Addr: ":8080",
Handler: app,
ReadHeaderTimeout: 5 * time.Second,
}
log.Fatal(srv.ListenAndServeTLS(certFile, keyFile))
Use the second form when you need timeouts, TLS configuration, or graceful shutdown. Listen calls http.ListenAndServe with no timeouts set.
Inspect registered routes
app.Routes() returns the *ezz.Route values, which is what the OpenAPI generator walks. It is also handy for a startup log or a route table in tests.
for _, r := range app.Routes() {
log.Printf("%-6s %-30s public=%t", r.Method, r.Pattern, r.AuthRule == nil || r.AuthRule.IsPublic())
}
Next steps
- Handlers for what happens inside a route.
- Middleware for the wrapping order and the built-in set.