Router
Basic Routing
Learn the fundamentals of HTTP routing with Forge
Forge's router provides a simple yet powerful way to handle HTTP requests. This guide covers the fundamentals of routing, including HTTP methods, path parameters, and basic route configuration.
HTTP Methods
Forge supports all standard HTTP methods with a consistent API:
// GET - Retrieve data
app.Router().GET("/users", getUsersHandler)
// POST - Create new resources
app.Router().POST("/users", createUserHandler)
// PUT - Update entire resources
app.Router().PUT("/users/:id", updateUserHandler)
// PATCH - Partial updates
app.Router().PATCH("/users/:id", patchUserHandler)
// DELETE - Remove resources
app.Router().DELETE("/users/:id", deleteUserHandler)
// OPTIONS - CORS preflight
app.Router().OPTIONS("/users", optionsHandler)
// HEAD - Get headers only
app.Router().HEAD("/users", headHandler)Handler Functions
Basic Handler
func homeHandler(ctx forge.Context) error {
return ctx.JSON(200, map[string]string{
"message": "Welcome to Forge!",
})
}
app.Router().GET("/", homeHandler)Inline Handlers
app.Router().GET("/", func(ctx forge.Context) error {
return ctx.JSON(200, map[string]string{
"message": "Hello, World!",
})
})Handler with Error Handling
func getUserHandler(ctx forge.Context) error {
id := ctx.Param("id")
user, err := userService.GetUser(id)
if err != nil {
if errors.Is(err, ErrUserNotFound) {
return forge.NotFound("user not found")
}
return forge.InternalError("failed to get user")
}
return ctx.JSON(200, user)
}Path Parameters
Single Parameter
// Route: /users/:id
app.Router().GET("/users/:id", func(ctx forge.Context) error {
id := ctx.Param("id")
return ctx.JSON(200, map[string]string{"id": id})
})Multiple Parameters
// Route: /users/:userId/posts/:postId
app.Router().GET("/users/:userId/posts/:postId", func(ctx forge.Context) error {
userID := ctx.Param("userId")
postID := ctx.Param("postId")
return ctx.JSON(200, map[string]string{
"userId": userID,
"postId": postID,
})
})Wildcard Routes
// Route: /files/*
app.Router().GET("/files/*", func(ctx forge.Context) error {
path := ctx.Param("*") // Gets everything after /files/
return ctx.JSON(200, map[string]string{"path": path})
})Request Handling
JSON Request Body
type CreateUserRequest struct {
Name string `json:"name" validate:"required"`
Email string `json:"email" validate:"required,email"`
}
func createUserHandler(ctx forge.Context) error {
var req CreateUserRequest
// Bind JSON request body
if err := ctx.BindJSON(&req); err != nil {
return forge.BadRequest("invalid JSON")
}
// Validate request
if err := validate.Struct(req); err != nil {
return forge.BadRequest("validation failed")
}
// Process request
user, err := userService.CreateUser(req)
if err != nil {
return forge.InternalError("failed to create user")
}
return ctx.JSON(201, user)
}Form Data
func updateUserHandler(ctx forge.Context) error {
var req UpdateUserRequest
// Bind form data
if err := ctx.BindForm(&req); err != nil {
return forge.BadRequest("invalid form data")
}
// Process update
user, err := userService.UpdateUser(req)
if err != nil {
return forge.InternalError("failed to update user")
}
return ctx.JSON(200, user)
}Query Parameters
func getUsersHandler(ctx forge.Context) error {
// Get query parameters
page := ctx.Query("page", "1")
limit := ctx.Query("limit", "10")
search := ctx.Query("search", "")
// Convert to integers
pageNum, _ := strconv.Atoi(page)
limitNum, _ := strconv.Atoi(limit)
// Build filter
filter := UserFilter{
Page: pageNum,
Limit: limitNum,
Search: search,
}
users, err := userService.GetUsers(filter)
if err != nil {
return forge.InternalError("failed to get users")
}
return ctx.JSON(200, users)
}Response Handling
JSON Response
func getUsersHandler(ctx forge.Context) error {
users := []User{
{ID: 1, Name: "John Doe", Email: "john@example.com"},
{ID: 2, Name: "Jane Smith", Email: "jane@example.com"},
}
return ctx.JSON(200, users)
}Custom Response
func downloadFileHandler(ctx forge.Context) error {
filename := ctx.Param("filename")
// Set custom headers
ctx.Header("Content-Type", "application/octet-stream")
ctx.Header("Content-Disposition", fmt.Sprintf("attachment; filename=%s", filename))
// Stream file content
file, err := os.Open(fmt.Sprintf("files/%s", filename))
if err != nil {
return forge.NotFound("file not found")
}
defer file.Close()
return ctx.Stream(200, "application/octet-stream", file)
}Error Responses
func errorHandler(ctx forge.Context) error {
// Different error types
return forge.BadRequest("invalid request")
return forge.Unauthorized("authentication required")
return forge.Forbidden("access denied")
return forge.NotFound("resource not found")
return forge.InternalError("server error")
// Custom error with status code
return forge.NewHTTPError(422, "validation failed", map[string]string{
"field": "email",
"error": "invalid format",
})
}Route Configuration
Basic Route Options
app.Router().GET("/users", handler,
forge.WithName("getUsers"),
forge.WithSummary("Get all users"),
forge.WithDescription("Retrieves a list of all users with pagination"),
forge.WithTags("users", "api"),
)Route with Timeout
app.Router().POST("/users", handler,
forge.WithTimeout(30*time.Second),
forge.WithName("createUser"),
)Route with Metadata
app.Router().GET("/users", handler,
forge.WithMetadata("version", "v1"),
forge.WithMetadata("deprecated", false),
forge.WithMetadata("rate_limit", "100/hour"),
)Route Groups
Basic Group
// API v1 group
apiV1 := app.Router().Group("/api/v1")
apiV1.GET("/users", getUsersHandler)
apiV1.POST("/users", createUserHandler)
apiV1.GET("/users/:id", getUserHandler)
apiV1.PUT("/users/:id", updateUserHandler)
apiV1.DELETE("/users/:id", deleteUserHandler)Group with Middleware
// Admin group with authentication
admin := app.Router().Group("/admin", forge.WithGroupMiddleware(authMiddleware))
admin.GET("/dashboard", dashboardHandler)
admin.POST("/settings", updateSettingsHandler)
admin.GET("/users", adminGetUsersHandler)Nested Groups
// API v1 group
apiV1 := app.Router().Group("/api/v1")
// Users sub-group
users := apiV1.Group("/users")
users.GET("/", getUsersHandler)
users.POST("/", createUserHandler)
// Posts sub-group
posts := apiV1.Group("/posts")
posts.GET("/", getPostsHandler)
posts.POST("/", createPostHandler)Route Inspection
List All Routes
func listRoutesHandler(ctx forge.Context) error {
routes := app.Router().Routes()
var routeList []map[string]interface{}
for _, route := range routes {
routeList = append(routeList, map[string]interface{}{
"name": route.Name,
"method": route.Method,
"path": route.Path,
"pattern": route.Pattern,
"tags": route.Tags,
})
}
return ctx.JSON(200, routeList)
}Find Route by Name
func getRouteInfoHandler(ctx forge.Context) error {
routeName := ctx.Param("name")
route, found := app.Router().RouteByName(routeName)
if !found {
return forge.NotFound("route not found")
}
return ctx.JSON(200, route)
}Find Routes by Tag
func getRoutesByTagHandler(ctx forge.Context) error {
tag := ctx.Param("tag")
routes := app.Router().RoutesByTag(tag)
return ctx.JSON(200, routes)
}Best Practices
- Use Descriptive Route Names: Choose clear, descriptive names for routes
- Consistent URL Structure: Follow RESTful conventions
- Proper Error Handling: Return appropriate HTTP status codes
- Input Validation: Validate all input data
- Use Route Groups: Organize related routes together
- Document Routes: Use OpenAPI options for documentation
- Handle Edge Cases: Consider all possible scenarios
Common Patterns
RESTful API Structure
// Users resource
users := app.Router().Group("/users")
users.GET("/", getUsersHandler) // GET /users
users.POST("/", createUserHandler) // POST /users
users.GET("/:id", getUserHandler) // GET /users/:id
users.PUT("/:id", updateUserHandler) // PUT /users/:id
users.DELETE("/:id", deleteUserHandler) // DELETE /users/:id
// Posts resource
posts := app.Router().Group("/posts")
posts.GET("/", getPostsHandler) // GET /posts
posts.POST("/", createPostHandler) // POST /posts
posts.GET("/:id", getPostHandler) // GET /posts/:id
posts.PUT("/:id", updatePostHandler) // PUT /posts/:id
posts.DELETE("/:id", deletePostHandler) // DELETE /posts/:idHealth Check Endpoint
app.Router().GET("/health", func(ctx forge.Context) error {
return ctx.JSON(200, map[string]string{
"status": "healthy",
"timestamp": time.Now().Format(time.RFC3339),
})
})API Versioning
// Version 1
v1 := app.Router().Group("/api/v1")
v1.GET("/users", getUsersV1Handler)
// Version 2
v2 := app.Router().Group("/api/v2")
v2.GET("/users", getUsersV2Handler)For more advanced routing features, see the Middleware and Route Groups documentation.
How is this guide?
Last updated on