response

package
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 15, 2026 License: MIT Imports: 4 Imported by: 0

README

Response Package

Standardized HTTP response formats for Nivo APIs, providing consistent success and error response envelopes.

Features

  • Standardized Response Envelope: Consistent structure for all API responses
  • Success Responses: Helper functions for common success scenarios
  • Error Responses: Integration with shared/errors for consistent error handling
  • Pagination Support: Built-in pagination metadata
  • Metadata: Request ID, timestamp, and custom metadata support
  • Type-Safe: Structured types for responses, errors, and metadata
  • HTTP Status Codes: Automatic status code mapping from errors

Installation

go get github.com/1mb-dev/nivomoney/shared/response

Response Structure

All responses follow a standardized envelope format:

{
  "success": true|false,
  "data": { ... },           // Present on success
  "error": { ... },          // Present on error
  "meta": {                  // Optional metadata
    "request_id": "...",
    "timestamp": "...",
    "pagination": { ... }
  }
}

Usage

Success Responses
import "github.com/1mb-dev/nivomoney/shared/response"

// 200 OK
func getUser(w http.ResponseWriter, r *http.Request) {
    user := map[string]interface{}{
        "id":    "123",
        "name":  "John Doe",
        "email": "john@example.com",
    }
    response.OK(w, user)
}

// 201 Created
func createUser(w http.ResponseWriter, r *http.Request) {
    newUser := map[string]interface{}{
        "id":   "456",
        "name": "Jane Doe",
    }
    response.Created(w, newUser)
}

// 204 No Content
func deleteUser(w http.ResponseWriter, r *http.Request) {
    // ... perform deletion
    response.NoContent(w)
}
Error Responses
// 400 Bad Request
func validateInput(w http.ResponseWriter, r *http.Request) {
    if err := validateRequest(r); err != nil {
        response.BadRequest(w, "invalid request format")
        return
    }
}

// 401 Unauthorized
func authenticate(w http.ResponseWriter, r *http.Request) {
    if !isAuthenticated(r) {
        response.Unauthorized(w, "invalid or expired token")
        return
    }
}

// 403 Forbidden
func authorize(w http.ResponseWriter, r *http.Request) {
    if !hasPermission(r) {
        response.Forbidden(w, "insufficient permissions")
        return
    }
}

// 404 Not Found
func getResource(w http.ResponseWriter, r *http.Request) {
    resource := findResource(id)
    if resource == nil {
        response.NotFound(w, "user")
        return
    }
    response.OK(w, resource)
}

// 409 Conflict
func createResource(w http.ResponseWriter, r *http.Request) {
    if exists(email) {
        response.Conflict(w, "email already exists")
        return
    }
}

// 500 Internal Server Error
func handleDatabaseError(w http.ResponseWriter, r *http.Request) {
    if err := db.Query(...); err != nil {
        response.InternalError(w, "database error")
        return
    }
}
Custom Error Responses

Use with shared/errors for full error control:

import (
    "github.com/1mb-dev/nivomoney/shared/errors"
    "github.com/1mb-dev/nivomoney/shared/response"
)

func transfer(w http.ResponseWriter, r *http.Request) {
    if balance < amount {
        err := errors.InsufficientFunds("insufficient balance")
        err.WithDetail("balance", balance).
            WithDetail("required", amount)
        response.Error(w, err)
        return
    }
}
Validation Errors
func validateUser(w http.ResponseWriter, r *http.Request) {
    validationErrors := map[string]interface{}{
        "email":    "invalid email format",
        "password": "must be at least 8 characters",
        "age":      "must be 18 or older",
    }

    response.ValidationError(w, validationErrors)
}

Response:

{
  "success": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "validation failed",
    "details": {
      "email": "invalid email format",
      "password": "must be at least 8 characters",
      "age": "must be 18 or older"
    }
  }
}
Pagination
func listUsers(w http.ResponseWriter, r *http.Request) {
    page := getPageFromQuery(r)     // e.g., 2
    pageSize := getPageSizeFromQuery(r) // e.g., 20

    users, total := getUsersFromDB(page, pageSize)

    response.Paginated(w, users, page, pageSize, total)
}

Response:

{
  "success": true,
  "data": [
    {"id": "1", "name": "User 1"},
    {"id": "2", "name": "User 2"}
  ],
  "meta": {
    "pagination": {
      "page": 2,
      "page_size": 20,
      "total_pages": 5,
      "total_items": 95,
      "has_next": true,
      "has_prev": true
    }
  }
}
Metadata

Include custom metadata in responses:

func getWithMetadata(w http.ResponseWriter, r *http.Request) {
    data := getUserData()

    meta := &response.Meta{
        RequestID: r.Context().Value(logger.RequestIDKey).(string),
        Timestamp: time.Now().UTC().Format(time.RFC3339),
    }

    response.SuccessWithMeta(w, http.StatusOK, data, meta)
}

Response:

{
  "success": true,
  "data": { "id": "123", "name": "John" },
  "meta": {
    "request_id": "abc123",
    "timestamp": "2024-01-01T12:00:00Z"
  }
}

API Reference

Success Functions
  • OK(w, data) - 200 OK response
  • Created(w, data) - 201 Created response
  • NoContent(w) - 204 No Content response
  • Success(w, statusCode, data) - Custom success response
  • SuccessWithMeta(w, statusCode, data, meta) - Success with metadata
Error Functions
  • BadRequest(w, message) - 400 Bad Request
  • Unauthorized(w, message) - 401 Unauthorized
  • Forbidden(w, message) - 403 Forbidden
  • NotFound(w, resource) - 404 Not Found
  • Conflict(w, message) - 409 Conflict
  • InternalError(w, message) - 500 Internal Server Error
  • ValidationError(w, details) - 400 with validation details
  • Error(w, err) - Custom error from errors.Error
  • ErrorWithMeta(w, err, meta) - Error with metadata
Pagination
  • Paginated(w, data, page, pageSize, totalItems) - Paginated response
Low-Level
  • JSON(w, statusCode, data) - Write raw JSON response

Types

Response
type Response struct {
    Success bool        `json:"success"`
    Data    interface{} `json:"data,omitempty"`
    Error   *ErrorData  `json:"error,omitempty"`
    Meta    *Meta       `json:"meta,omitempty"`
}
ErrorData
type ErrorData struct {
    Code    string                 `json:"code"`
    Message string                 `json:"message"`
    Details map[string]interface{} `json:"details,omitempty"`
}
Meta
type Meta struct {
    RequestID  string      `json:"request_id,omitempty"`
    Timestamp  string      `json:"timestamp,omitempty"`
    Pagination *Pagination `json:"pagination,omitempty"`
}
Pagination
type Pagination struct {
    Page       int   `json:"page"`
    PageSize   int   `json:"page_size"`
    TotalPages int   `json:"total_pages"`
    TotalItems int64 `json:"total_items"`
    HasNext    bool  `json:"has_next"`
    HasPrev    bool  `json:"has_prev"`
}

Examples

Complete CRUD API
package main

import (
    "encoding/json"
    "net/http"

    "github.com/1mb-dev/nivomoney/shared/errors"
    "github.com/1mb-dev/nivomoney/shared/response"
)

type User struct {
    ID    string `json:"id"`
    Name  string `json:"name"`
    Email string `json:"email"`
}

// List users with pagination
func listUsers(w http.ResponseWriter, r *http.Request) {
    page := 1
    pageSize := 20

    users, total := db.GetUsers(page, pageSize)
    response.Paginated(w, users, page, pageSize, total)
}

// Get single user
func getUser(w http.ResponseWriter, r *http.Request) {
    id := r.URL.Query().Get("id")

    user := db.FindUser(id)
    if user == nil {
        response.NotFound(w, "user")
        return
    }

    response.OK(w, user)
}

// Create user
func createUser(w http.ResponseWriter, r *http.Request) {
    var user User
    if err := json.NewDecoder(r.Body).Decode(&user); err != nil {
        response.BadRequest(w, "invalid request body")
        return
    }

    // Validate
    if validationErrors := validate(user); len(validationErrors) > 0 {
        response.ValidationError(w, validationErrors)
        return
    }

    // Check for duplicates
    if db.UserExists(user.Email) {
        response.Conflict(w, "email already exists")
        return
    }

    // Create
    created, err := db.CreateUser(user)
    if err != nil {
        response.InternalError(w, "failed to create user")
        return
    }

    response.Created(w, created)
}

// Update user
func updateUser(w http.ResponseWriter, r *http.Request) {
    id := r.URL.Query().Get("id")

    var updates User
    if err := json.NewDecoder(r.Body).Decode(&updates); err != nil {
        response.BadRequest(w, "invalid request body")
        return
    }

    user := db.FindUser(id)
    if user == nil {
        response.NotFound(w, "user")
        return
    }

    updated, err := db.UpdateUser(id, updates)
    if err != nil {
        response.InternalError(w, "failed to update user")
        return
    }

    response.OK(w, updated)
}

// Delete user
func deleteUser(w http.ResponseWriter, r *http.Request) {
    id := r.URL.Query().Get("id")

    if !db.UserExists(id) {
        response.NotFound(w, "user")
        return
    }

    if err := db.DeleteUser(id); err != nil {
        response.InternalError(w, "failed to delete user")
        return
    }

    response.NoContent(w)
}
Error Handling with Context
func transfer(w http.ResponseWriter, r *http.Request) {
    var req TransferRequest
    json.NewDecoder(r.Body).Decode(&req)

    // Get account
    account := db.GetAccount(req.FromAccountID)
    if account == nil {
        response.NotFound(w, "account")
        return
    }

    // Check balance
    if account.Balance < req.Amount {
        err := errors.InsufficientFunds("insufficient balance")
        err.WithDetail("balance", account.Balance).
            WithDetail("required", req.Amount).
            WithDetail("shortfall", req.Amount - account.Balance)
        response.Error(w, err)
        return
    }

    // Check if account is frozen
    if account.Status == "frozen" {
        err := errors.AccountFrozen("account is frozen")
        err.WithDetail("account_id", account.ID).
            WithDetail("frozen_at", account.FrozenAt)
        response.Error(w, err)
        return
    }

    // Perform transfer
    result, err := db.Transfer(req)
    if err != nil {
        response.InternalError(w, "transfer failed")
        return
    }

    response.OK(w, result)
}

Best Practices

  1. Always use response helpers - Don't write raw JSON responses
  2. Use semantic HTTP status codes - Match status to the actual result
  3. Include validation details - Help clients understand what went wrong
  4. Add request IDs - Include in metadata for request tracing
  5. Paginate large collections - Always paginate list endpoints
  6. Document error codes - Make error codes discoverable
  7. Be consistent - Use the same response format across all endpoints
  8. Handle errors gracefully - Return appropriate status codes and messages

Integration with Middleware

Works seamlessly with shared/middleware:

import (
    "github.com/1mb-dev/nivomoney/shared/logger"
    "github.com/1mb-dev/nivomoney/shared/middleware"
    "github.com/1mb-dev/nivomoney/shared/response"
)

func handler(w http.ResponseWriter, r *http.Request) {
    // Request ID is available from middleware
    requestID := r.Context().Value(logger.RequestIDKey)

    data := getData()

    meta := &response.Meta{
        RequestID: requestID.(string),
        Timestamp: time.Now().UTC().Format(time.RFC3339),
    }

    response.SuccessWithMeta(w, http.StatusOK, data, meta)
}

func main() {
    log := logger.NewDefault("api")

    app := middleware.Chain(
        handler,
        middleware.Recovery(log),
        middleware.RequestID(),
        middleware.Logging(log),
    )

    http.ListenAndServe(":8080", app)
}

Testing

All response functions have 100% test coverage:

cd shared/response
go test -v
go test -cover

Performance

Response functions are lightweight:

  • JSON encoding: Standard library encoding/json
  • No reflection overhead in hot path
  • Minimal allocations for common cases
  • ~1-2µs per response (excluding JSON marshaling)

License

Copyright © 2025 Nivo

Documentation

Overview

Package response provides standardized HTTP response formats for Nivo APIs.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func BadRequest

func BadRequest(w http.ResponseWriter, message string)

BadRequest writes a 400 Bad Request response.

func Conflict

func Conflict(w http.ResponseWriter, message string)

Conflict writes a 409 Conflict response.

func Created

func Created(w http.ResponseWriter, data interface{})

Created writes a 201 Created response.

func Error

func Error(w http.ResponseWriter, err *errors.Error)

Error writes an error response from an errors.Error.

func ErrorWithMeta

func ErrorWithMeta(w http.ResponseWriter, err *errors.Error, meta *Meta)

ErrorWithMeta writes an error response with metadata.

func Forbidden

func Forbidden(w http.ResponseWriter, message string)

Forbidden writes a 403 Forbidden response.

func InternalError

func InternalError(w http.ResponseWriter, message string)

InternalError writes a 500 Internal Server Error response.

func JSON

func JSON(w http.ResponseWriter, statusCode int, data interface{})

JSON writes a JSON response with the given status code and data. Errors during encoding are logged but not returned since the HTTP connection may already be broken and there's no meaningful recovery action.

func NoContent

func NoContent(w http.ResponseWriter)

NoContent writes a 204 No Content response.

func NotFound

func NotFound(w http.ResponseWriter, resource string)

NotFound writes a 404 Not Found response.

func OK

func OK(w http.ResponseWriter, data interface{})

OK writes a 200 OK response.

func Paginated

func Paginated(w http.ResponseWriter, data interface{}, page, pageSize int, totalItems int64)

Paginated writes a paginated response with metadata.

func Success

func Success(w http.ResponseWriter, statusCode int, data interface{})

Success writes a success response.

func SuccessWithMeta

func SuccessWithMeta(w http.ResponseWriter, statusCode int, data interface{}, meta *Meta)

SuccessWithMeta writes a success response with metadata.

func Unauthorized

func Unauthorized(w http.ResponseWriter, message string)

Unauthorized writes a 401 Unauthorized response.

func ValidationError

func ValidationError(w http.ResponseWriter, details map[string]interface{})

ValidationError writes a validation error response.

Types

type ErrorData

type ErrorData struct {
	Code    string                 `json:"code"`
	Message string                 `json:"message"`
	Details map[string]interface{} `json:"details,omitempty"`
}

ErrorData contains error information.

type Meta

type Meta struct {
	RequestID  string      `json:"request_id,omitempty"`
	Timestamp  string      `json:"timestamp,omitempty"`
	Pagination *Pagination `json:"pagination,omitempty"`
}

Meta contains metadata about the response.

type Pagination

type Pagination struct {
	Page       int   `json:"page"`
	PageSize   int   `json:"page_size"`
	TotalPages int   `json:"total_pages"`
	TotalItems int64 `json:"total_items"`
	HasNext    bool  `json:"has_next"`
	HasPrev    bool  `json:"has_prev"`
}

Pagination contains pagination information.

type Response

type Response struct {
	Success bool        `json:"success"`
	Data    interface{} `json:"data,omitempty"`
	Error   *ErrorData  `json:"error,omitempty"`
	Meta    *Meta       `json:"meta,omitempty"`
}

Response represents a standardized API response envelope.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL