Context

Access request data and send responses

The Context interface is the central abstraction for handling HTTP requests in Forge. Every handler receives a Context that provides methods to read request data, bind payloads, manage cookies, access the DI container, and send responses.

func handler(ctx forge.Context) error {
    name := ctx.Query("name")
    return ctx.JSON(200, map[string]string{"hello": name})
}

Request Access

Access the underlying *http.Request and http.ResponseWriter directly when you need full control.

func handler(ctx forge.Context) error {
    r := ctx.Request()       // *http.Request
    w := ctx.Response()      // http.ResponseWriter

    // Access raw request properties
    method := r.Method
    url := r.URL.String()
    body := r.Body

    return ctx.JSON(200, map[string]string{
        "method": method,
        "url":    url,
    })
}

Path Parameters

Use Param to extract named path parameters defined in your route pattern.

// Route: router.GET("/users/:id", handler)
func handler(ctx forge.Context) error {
    userID := ctx.Param("id") // string

    // Route: /users/:id/posts/:postId
    postID := ctx.Param("postId")

    return ctx.JSON(200, map[string]string{
        "userId": userID,
        "postId": postID,
    })
}

Query Parameters

Use Query to read URL query string values.

// GET /search?q=forge&page=2&limit=20
func searchHandler(ctx forge.Context) error {
    query := ctx.Query("q")      // "forge"
    page := ctx.Query("page")    // "2"
    limit := ctx.Query("limit")  // "20"

    return ctx.JSON(200, map[string]string{
        "query": query,
        "page":  page,
        "limit": limit,
    })
}

Headers

Use Header to read request headers.

func handler(ctx forge.Context) error {
    contentType := ctx.Header("Content-Type")
    auth := ctx.Header("Authorization")
    requestID := ctx.Header("X-Request-ID")

    return ctx.JSON(200, map[string]string{
        "contentType": contentType,
        "auth":        auth,
        "requestId":   requestID,
    })
}

Request Binding

Forge provides type-safe binding methods that deserialize request data into Go structs. Binding validates the data and returns an error if the payload is malformed.

type CreateUserRequest struct {
    Name  string `json:"name"`
    Email string `json:"email"`
    Age   int    `json:"age"`
}

func createUser(ctx forge.Context) error {
    var req CreateUserRequest
    if err := ctx.BindJSON(&req); err != nil {
        return forge.BadRequest("invalid JSON: " + err.Error())
    }

    // req.Name, req.Email, req.Age are populated
    return ctx.JSON(201, req)
}
type ContactForm struct {
    Name    string `form:"name"`
    Email   string `form:"email"`
    Message string `form:"message"`
}

func submitContact(ctx forge.Context) error {
    var form ContactForm
    if err := ctx.BindForm(&form); err != nil {
        return forge.BadRequest("invalid form data: " + err.Error())
    }

    return ctx.JSON(200, map[string]string{
        "status": "received",
        "name":   form.Name,
    })
}
type SearchParams struct {
    Query  string `query:"q"`
    Page   int    `query:"page"`
    Limit  int    `query:"limit"`
    SortBy string `query:"sort_by"`
}

// GET /search?q=forge&page=1&limit=20&sort_by=relevance
func search(ctx forge.Context) error {
    var params SearchParams
    if err := ctx.BindQuery(&params); err != nil {
        return forge.BadRequest("invalid query params: " + err.Error())
    }

    return ctx.JSON(200, params)
}
type APIHeaders struct {
    Authorization string `header:"Authorization"`
    ContentType   string `header:"Content-Type"`
    RequestID     string `header:"X-Request-ID"`
}

func handler(ctx forge.Context) error {
    var headers APIHeaders
    if err := ctx.BindHeader(&headers); err != nil {
        return forge.BadRequest("invalid headers: " + err.Error())
    }

    return ctx.JSON(200, map[string]string{
        "requestId": headers.RequestID,
    })
}

File Uploads

Handle single and multiple file uploads with FormFile and FormFiles.

// Single file upload
func uploadAvatar(ctx forge.Context) error {
    file, header, err := ctx.FormFile("avatar")
    if err != nil {
        return forge.BadRequest("missing file: " + err.Error())
    }
    defer file.Close()

    // header.Filename - original filename
    // header.Size     - file size in bytes
    // header.Header   - MIME header
    data, err := io.ReadAll(file)
    if err != nil {
        return forge.InternalError(err)
    }

    return ctx.JSON(200, map[string]any{
        "filename": header.Filename,
        "size":     header.Size,
        "bytes":    len(data),
    })
}

// Multiple file upload
func uploadDocuments(ctx forge.Context) error {
    headers, err := ctx.FormFiles("documents")
    if err != nil {
        return forge.BadRequest("missing files: " + err.Error())
    }

    names := make([]string, len(headers))
    for i, h := range headers {
        names[i] = h.Filename
    }

    return ctx.JSON(200, map[string]any{
        "count": len(headers),
        "files": names,
    })
}

Sending Responses

Context provides multiple response methods for different content types.

func handler(ctx forge.Context) error {
    type User struct {
        ID    string `json:"id"`
        Name  string `json:"name"`
        Email string `json:"email"`
    }

    user := User{
        ID:    "usr_123",
        Name:  "Alice",
        Email: "alice@example.com",
    }

    return ctx.JSON(200, user)
}
func handler(ctx forge.Context) error {
    return ctx.String(200, "Hello, World!")
}
func handler(ctx forge.Context) error {
    data := []byte{0x50, 0x4B, 0x03, 0x04} // ZIP header
    return ctx.Bytes(200, data)
}
func handler(ctx forge.Context) error {
    html := "<h1>Welcome</h1><p>Hello from Forge!</p>"
    return ctx.HTML(200, html)
}

Context Values

Store and retrieve arbitrary values within the request lifecycle using Set and Get. This is useful for passing data between middleware and handlers.

// Middleware that extracts the authenticated user
func authMiddleware(next forge.Handler) forge.Handler {
    return func(ctx forge.Context) error {
        token := ctx.Header("Authorization")
        user, err := validateToken(token)
        if err != nil {
            return forge.Unauthorized("invalid token")
        }

        ctx.Set("user", user)
        ctx.Set("tenant_id", user.TenantID)
        return next(ctx)
    }
}

// Handler that reads context values
func handler(ctx forge.Context) error {
    user := ctx.Get("user").(*User)
    tenantID := ctx.Get("tenant_id").(string)

    return ctx.JSON(200, map[string]any{
        "user":     user.Name,
        "tenantId": tenantID,
    })
}

// Retrieve all stored values
func debugHandler(ctx forge.Context) error {
    all := ctx.GetAll() // map[string]any
    return ctx.JSON(200, all)
}

Read and set HTTP cookies.

func handler(ctx forge.Context) error {
    // Read a cookie
    sessionCookie, err := ctx.Cookie("session_id")
    if err != nil {
        // Cookie not found
        return forge.Unauthorized("no session")
    }

    // Set a cookie
    ctx.SetCookie(&http.Cookie{
        Name:     "session_id",
        Value:    "abc123",
        Path:     "/",
        MaxAge:   86400,        // 24 hours
        HttpOnly: true,
        Secure:   true,
        SameSite: http.SameSiteLaxMode,
    })

    return ctx.JSON(200, map[string]string{
        "session": sessionCookie.Value,
    })
}

DI Container Access

Access the dependency injection container from any handler to resolve services.

func handler(ctx forge.Context) error {
    container := ctx.Container()

    // Resolve a service by name
    userService, err := forge.Inject[*UserService](container)
    if err != nil {
        return forge.InternalError(err)
    }

    // Resolve by type (constructor injection)
    db, err := forge.InjectType[*Database](container)
    if err != nil {
        return forge.InternalError(err)
    }

    users, err := userService.List(ctx.Request().Context())
    if err != nil {
        return forge.InternalError(err)
    }

    return ctx.JSON(200, users)
}

Complete Example

A production handler combining multiple Context features.

type CreateOrderRequest struct {
    ProductID string `json:"product_id"`
    Quantity  int    `json:"quantity"`
}

type Order struct {
    ID        string `json:"id"`
    ProductID string `json:"product_id"`
    Quantity  int    `json:"quantity"`
    UserID    string `json:"user_id"`
    TenantID  string `json:"tenant_id"`
}

func createOrder(ctx forge.Context) error {
    // Path parameter
    tenantID := ctx.Param("tenantId")

    // Context value set by auth middleware
    userID := ctx.Get("user_id").(string)

    // Bind JSON body
    var req CreateOrderRequest
    if err := ctx.BindJSON(&req); err != nil {
        return forge.BadRequest("invalid request body")
    }

    // Resolve service from DI container
    orderService, err := forge.Inject[*OrderService](ctx.Container())
    if err != nil {
        return forge.InternalError(err)
    }

    // Create the order
    order, err := orderService.Create(ctx.Request().Context(), Order{
        ProductID: req.ProductID,
        Quantity:  req.Quantity,
        UserID:    userID,
        TenantID:  tenantID,
    })
    if err != nil {
        return forge.InternalError(err)
    }

    return ctx.JSON(201, order)
}

Context Interface Reference

MethodSignatureDescription
RequestRequest() *http.RequestUnderlying HTTP request
ResponseResponse() http.ResponseWriterUnderlying response writer
ParamParam(name string) stringPath parameter value
QueryQuery(name string) stringQuery string value
HeaderHeader(name string) stringRequest header value
BindJSONBindJSON(v any) errorBind JSON body to struct
BindFormBindForm(v any) errorBind form data to struct
BindQueryBindQuery(v any) errorBind query params to struct
BindHeaderBindHeader(v any) errorBind headers to struct
FormFileFormFile(name string) (File, *FileHeader, error)Single file upload
FormFilesFormFiles(name string) ([]*FileHeader, error)Multiple file uploads
JSONJSON(status int, data any) errorSend JSON response
StringString(status int, data string) errorSend text response
BytesBytes(status int, data []byte) errorSend raw bytes
HTMLHTML(status int, html string) errorSend HTML response
GetGet(key string) anyGet context value
SetSet(key string, value any)Set context value
GetAllGetAll() map[string]anyGet all context values
CookieCookie(name string) (*http.Cookie, error)Read cookie
SetCookieSetCookie(cookie *http.Cookie)Set cookie
ContainerContainer() ContainerDI container access

How is this guide?

On this page