API Error Types in fabrica-kit: A Complete Guide to Structured Error Handling

The fabrica-kit xerrors package provides centralized API error constructors that generate Kratos-compatible errors with specific HTTP status codes and machine-readable reason codes, enabling consistent error responses across Go microservices.

The go-pantheon/fabrica-kit repository provides a robust toolkit for building Go microservices, with its xerrors package serving as the central hub for API-level error handling. Understanding the available API error types in fabrica-kit is essential for developers who want to implement consistent, structured error responses that integrate seamlessly with the Kratos framework.

Overview of fabrica-kit Error Architecture

All API errors in fabrica-kit are defined in xerrors/apierrors.go. The architecture follows a two-tier approach:

  1. Error Constructors – Functions like APIParamInvalid() or APINotFound() accept a format string and variadic arguments, allowing you to create contextual error messages while preserving the standard HTTP status code and machine-readable reason code.

  2. Pre-Instantiated Defaults – Variables such as ErrAPIParamInvalid or ErrAPINotFound hold default instances of these errors. Use these when you do not need to customize the error message.

Every error created through these constructors returns a *errors.Error type from the Kratos framework, ensuring automatic HTTP response serialization and status code mapping.

Complete List of API Error Types in fabrica-kit

The xerrors package provides 16 distinct error constructors covering client errors (4xx), authentication failures (401), and internal server errors (500).

Client Errors (4xx)

These errors indicate problems with the request itself.

  • APIParamInvalid(message string, a ...any) – Returns 400 Bad Request with reason code PARAM_INVALID. Use this for malformed request bodies or missing required fields. Defined in xerrors/apierrors.go:48-56.

  • APIPageParamInvalid(message string, a ...any) – Returns 400 Bad Request with reason code PAGE_PARAM_INVALID. Specific to pagination parameter violations. Defined in xerrors/apierrors.go:58-66.

  • APIStatusIllegal(message string, a ...any) – Returns 403 Forbidden with reason code STATUS_ILLEGAL. Use when an operation is forbidden due to business logic or state constraints. Defined in xerrors/apierrors.go:38-46.

  • APINotFound(message string, a ...any) – Returns 404 Not Found with reason code NOT_FOUND. Use when a requested resource does not exist. Defined in xerrors/apierrors.go:68-76.

Conflict Errors (409)

These errors indicate resource state conflicts that prevent the operation from completing.

  • APIAlreadyExists(message string, a ...any) – Returns 409 Conflict with reason code ALREADY_EXISTS. Use when attempting to create a resource that already exists. Defined in xerrors/apierrors.go:78-86.

  • APIStateUpdateFailed(message string, a ...any) – Returns 409 Conflict with reason code STATE_UPDATE_FAILED. Use when a state transition cannot be performed. Defined in xerrors/apierrors.go:88-96.

  • APIDBNoAffected(message string, a ...any) – Returns 409 Conflict with reason code DB_NO_AFFECTED. Use when an UPDATE or DELETE operation affects zero rows, indicating the resource was modified or deleted concurrently. Defined in xerrors/apierrors.go:158-166.

Authentication Errors (401)

These errors indicate failures in authentication or session management.

  • APISessionIllegal(message string, a ...any) – Returns 401 Unauthorized with reason code SESSION_ILLEGAL. Use for malformed or unauthorized sessions. Defined in xerrors/apierrors.go:98-106.

  • APISessionTimeout(message string, a ...any) – Returns 401 Unauthorized with reason code SESSION_TIMEOUT. Use for expired sessions. Defined in xerrors/apierrors.go:108-116.

  • APIAuthFailed(message string, a ...any) – Returns 401 Unauthorized with reason code AUTH_FAILED. Use for general authentication failures such as bad credentials. Defined in xerrors/apierrors.go:118-126.

  • APIPlatformAuthFailed(message string, a ...any) – Returns 401 Unauthorized with reason code PLATFORM_AUTH_FAILED. Use for platform-level authentication problems. Defined in xerrors/apierrors.go:128-136.

Internal Server Errors (500)

These errors indicate infrastructure or unrecoverable application failures.

  • APICodecFailed(message string, a ...any) – Returns 500 Internal Server Error with reason code CODEC_FAILED. Use for serialization or deserialization errors. Defined in xerrors/apierrors.go:138-146.

  • APIDBFailed(message string, a ...any) – Returns 500 Internal Server Error with reason code DB_FAILED. Use for generic database errors. Defined in xerrors/apierrors.go:148-156.

How to Use fabrica-kit API Error Types

Using Error Constructors with Custom Messages

When you need to provide context-specific error messages, call the constructor functions directly from your service layer. These functions accept printf-style formatting arguments.

package user

import (
	"context"
	"github.com/go-pantheon/fabrica-kit/xerrors"
)

func (s *Service) GetUser(ctx context.Context, id string) (*User, error) {
	if id == "" {
		// Returns 400 Bad Request with reason PARAM_INVALID
		return nil, xerrors.APIParamInvalid("user id is required, got empty string")
	}
	
	user, err := s.repo.Find(ctx, id)
	if err != nil {
		return nil, xerrors.APIDBFailed("database query failed for user %s: %v", id, err)
	}
	
	if user == nil {
		// Returns 404 Not Found with reason NOT_FOUND
		return nil, xerrors.APINotFound("user with id %s does not exist", id)
	}
	
	return user, nil
}

Using Pre-Instantiated Default Errors

For common validation failures where a generic message suffices, use the package-level variables defined in xerrors/apierrors.go lines 11-35. These variables reduce allocation overhead and standardize error messages across your application.

func createHandler(c *gin.Context) {
	var req CreateReq
	if err := c.BindJSON(&req); err != nil {
		// Uses default message "default param invalid"
		c.Error(xerrors.ErrAPIParamInvalid)
		return
	}
	// Business logic...
}

Available pre-instantiated errors include:

  • ErrAPIStatusIllegal
  • ErrAPIParamInvalid
  • ErrAPIPageParamInvalid
  • ErrAPINotFound
  • ErrAPIAlreadyExists
  • ErrAPIStateUpdateFailed
  • ErrAPISessionIllegal
  • ErrAPISessionTimeout
  • ErrAPIAuthFailed
  • ErrAPIPlatformAuthFailed
  • ErrAPICodecFailed
  • ErrAPIDBFailed
  • ErrAPIDBNoAffected

Handling Database-Specific Conflicts

When updating resources, detect zero-row updates to prevent silent failures or race conditions. Wrap the low-level ErrDBRecordNotAffected (from xerrors/kiterrors.go:45-47) with the appropriate API error constructor.

func (s *Service) UpdateProfile(ctx context.Context, p *Profile) error {
	affected, err := s.repo.Update(ctx, p)
	if err != nil {
		return xerrors.APIDBFailed("update failed: %w", err)
	}
	
	if affected == 0 {
		// Returns 409 Conflict with reason DB_NO_AFFECTED
		return xerrors.APIDBNoAffected("profile %s was modified or deleted concurrently", p.ID)
	}
	
	return nil
}

Kratos HTTP Response Integration

fabrica-kit leverages the Kratos framework's built-in error handling middleware. When you return any error created via the xerrors constructors, Kratos automatically:

  1. Extracts the HTTP status code from the error definition (400, 401, 403, 404, 409, or 500).
  2. Retrieves the machine-readable reason code (e.g., PARAM_INVALID, NOT_FOUND) via errors.Reason(err).
  3. Serializes the response as JSON with the following structure:
{
  "code": 409,
  "reason": "ALREADY_EXISTS",
  "message": "user with email user@example.com already exists"
}

This integration eliminates the need for manual HTTP response construction in your handlers. Simply return the appropriate xerrors type, and the framework ensures consistent, typed error responses across all API endpoints.

Summary

  • fabrica-kit centralizes API error handling in the xerrors package, providing 16+ typed error constructors.
  • Each error type maps to a specific HTTP status code (400, 401, 403, 404, 409, 500) and machine-readable reason code.
  • Constructors like APIParamInvalid() allow custom messages with formatting, while pre-instantiated variables like ErrAPIParamInvalid provide zero-allocation defaults.
  • The APIDBNoAffected constructor specifically handles database race conditions by returning HTTP 409 Conflict when updates affect zero rows.
  • Kratos framework integration ensures automatic JSON serialization and proper HTTP status code mapping without manual response handling.

Frequently Asked Questions

How do I choose between using a constructor and a pre-instantiated error variable?

Use pre-instantiated variables (e.g., xerrors.ErrAPIParamInvalid) when the default error message is sufficient for the scenario, as they avoid memory allocation and ensure message consistency across your application. Use constructors (e.g., xerrors.APIParamInvalid("field %s is required", fieldName)) when you need to provide context-specific details that help clients debug the exact cause of the failure.

What is the difference between APIDBFailed and APIDBNoAffected?

APIDBFailed represents generic database infrastructure failures (connection errors, query timeouts, syntax errors) and returns HTTP 500 Internal Server Error with reason code DB_FAILED. APIDBNoAffected specifically indicates that an UPDATE or DELETE operation completed successfully but modified zero rows, typically due to concurrent modification or deletion, and returns HTTP 409 Conflict with reason code DB_NO_AFFECTED.

How can I customize the error message while keeping the standard HTTP status code?

Call the error constructor functions with your custom message using printf-style formatting. For example, xerrors.APINotFound("user %s not found in tenant %s", userID, tenantID) will return a 404 Not Found response with reason code NOT_FOUND, but the message field in the JSON response will contain your specific text rather than the generic default.

Are these errors compatible with standard Go error wrapping?

Yes, the errors returned by fabrica-kit constructors implement the standard Go error interface and work with errors.Is() and errors.As(). However, for Kratos to properly serialize the HTTP response, you should return the error directly rather than wrapping it with fmt.Errorf("%w", err), as excessive wrapping may obscure the reason code and HTTP status metadata that Kratos extracts from the error structure.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →