Skip to main content

RESTful Patterns

The Fiber client is designed to make building RESTful API clients straightforward and efficient. This guide shows common patterns for CRUD operations and API interactions.

Basic REST Client

Create a client configured for a REST API:
import (
    "github.com/gofiber/fiber/v3/client"
    "time"
)

func NewAPIClient(baseURL, apiKey string) *client.Client {
    c := client.New()
    
    c.SetBaseURL(baseURL)
    c.SetTimeout(30 * time.Second)
    
    // Set default headers
    c.SetHeader("Accept", "application/json")
    c.SetHeader("Content-Type", "application/json")
    c.SetHeader("Authorization", "Bearer "+apiKey)
    
    return c
}

// Usage
c := NewAPIClient("https://api.example.com/v1", "your-api-key")

CRUD Operations

Implement standard CRUD operations:

Create (POST)

Create a new resource:
type User struct {
    ID    int    `json:"id,omitempty"`
    Name  string `json:"name"`
    Email string `json:"email"`
}

func CreateUser(c *client.Client, user User) (*User, error) {
    resp, err := c.R().
        SetJSON(user).
        Post("/users")
    if err != nil {
        return nil, err
    }
    defer resp.Close()
    
    if resp.StatusCode() != 201 {
        return nil, fmt.Errorf("unexpected status: %d", resp.StatusCode())
    }
    
    var createdUser User
    if err := resp.JSON(&createdUser); err != nil {
        return nil, err
    }
    
    return &createdUser, nil
}

// Usage
newUser := User{Name: "John Doe", Email: "[email protected]"}
user, err := CreateUser(c, newUser)
if err != nil {
    panic(err)
}

fmt.Printf("Created user ID: %d\n", user.ID)

Read (GET)

Retrieve a single resource:
func GetUser(c *client.Client, id int) (*User, error) {
    resp, err := c.R().
        SetPathParam("id", strconv.Itoa(id)).
        Get("/users/:id")
    if err != nil {
        return nil, err
    }
    defer resp.Close()
    
    if resp.StatusCode() != 200 {
        return nil, fmt.Errorf("user not found: %d", resp.StatusCode())
    }
    
    var user User
    if err := resp.JSON(&user); err != nil {
        return nil, err
    }
    
    return &user, nil
}

// Usage
user, err := GetUser(c, 123)
if err != nil {
    panic(err)
}

fmt.Printf("User: %s (%s)\n", user.Name, user.Email)

List (GET)

Retrieve multiple resources with pagination:
type UserListParams struct {
    Page     int    `param:"page"`
    PageSize int    `param:"page_size"`
    Sort     string `param:"sort"`
}

type UserListResponse struct {
    Users      []User `json:"users"`
    Total      int    `json:"total"`
    Page       int    `json:"page"`
    TotalPages int    `json:"total_pages"`
}

func ListUsers(c *client.Client, params UserListParams) (*UserListResponse, error) {
    resp, err := c.R().
        SetParamsWithStruct(params).
        Get("/users")
    if err != nil {
        return nil, err
    }
    defer resp.Close()
    
    if resp.StatusCode() != 200 {
        return nil, fmt.Errorf("failed to list users: %d", resp.StatusCode())
    }
    
    var result UserListResponse
    if err := resp.JSON(&result); err != nil {
        return nil, err
    }
    
    return &result, nil
}

// Usage
params := UserListParams{
    Page:     1,
    PageSize: 10,
    Sort:     "name",
}

result, err := ListUsers(c, params)
if err != nil {
    panic(err)
}

fmt.Printf("Found %d users (page %d/%d)\n", len(result.Users), result.Page, result.TotalPages)

Update (PUT/PATCH)

Update an existing resource:
func UpdateUser(c *client.Client, id int, user User) (*User, error) {
    resp, err := c.R().
        SetPathParam("id", strconv.Itoa(id)).
        SetJSON(user).
        Put("/users/:id")
    if err != nil {
        return nil, err
    }
    defer resp.Close()
    
    if resp.StatusCode() != 200 {
        return nil, fmt.Errorf("failed to update user: %d", resp.StatusCode())
    }
    
    var updatedUser User
    if err := resp.JSON(&updatedUser); err != nil {
        return nil, err
    }
    
    return &updatedUser, nil
}

// Partial update with PATCH
func PartialUpdateUser(c *client.Client, id int, updates map[string]interface{}) (*User, error) {
    resp, err := c.R().
        SetPathParam("id", strconv.Itoa(id)).
        SetJSON(updates).
        Patch("/users/:id")
    if err != nil {
        return nil, err
    }
    defer resp.Close()
    
    if resp.StatusCode() != 200 {
        return nil, fmt.Errorf("failed to update user: %d", resp.StatusCode())
    }
    
    var user User
    if err := resp.JSON(&user); err != nil {
        return nil, err
    }
    
    return &user, nil
}

// Usage
updates := map[string]interface{}{
    "email": "[email protected]",
}

user, err := PartialUpdateUser(c, 123, updates)
if err != nil {
    panic(err)
}

Delete (DELETE)

Delete a resource:
func DeleteUser(c *client.Client, id int) error {
    resp, err := c.R().
        SetPathParam("id", strconv.Itoa(id)).
        Delete("/users/:id")
    if err != nil {
        return err
    }
    defer resp.Close()
    
    if resp.StatusCode() != 204 && resp.StatusCode() != 200 {
        return fmt.Errorf("failed to delete user: %d", resp.StatusCode())
    }
    
    return nil
}

// Usage
if err := DeleteUser(c, 123); err != nil {
    panic(err)
}

fmt.Println("User deleted successfully")

Resource Client Pattern

Organize API operations into a resource client:
type UserClient struct {
    client *client.Client
}

func NewUserClient(c *client.Client) *UserClient {
    return &UserClient{client: c}
}

func (uc *UserClient) Create(user User) (*User, error) {
    resp, err := uc.client.R().
        SetJSON(user).
        Post("/users")
    if err != nil {
        return nil, err
    }
    defer resp.Close()
    
    var result User
    if err := resp.JSON(&result); err != nil {
        return nil, err
    }
    
    return &result, nil
}

func (uc *UserClient) Get(id int) (*User, error) {
    resp, err := uc.client.R().
        SetPathParam("id", strconv.Itoa(id)).
        Get("/users/:id")
    if err != nil {
        return nil, err
    }
    defer resp.Close()
    
    var user User
    if err := resp.JSON(&user); err != nil {
        return nil, err
    }
    
    return &user, nil
}

func (uc *UserClient) List(params UserListParams) (*UserListResponse, error) {
    resp, err := uc.client.R().
        SetParamsWithStruct(params).
        Get("/users")
    if err != nil {
        return nil, err
    }
    defer resp.Close()
    
    var result UserListResponse
    if err := resp.JSON(&result); err != nil {
        return nil, err
    }
    
    return &result, nil
}

func (uc *UserClient) Update(id int, user User) (*User, error) {
    resp, err := uc.client.R().
        SetPathParam("id", strconv.Itoa(id)).
        SetJSON(user).
        Put("/users/:id")
    if err != nil {
        return nil, err
    }
    defer resp.Close()
    
    var result User
    if err := resp.JSON(&result); err != nil {
        return nil, err
    }
    
    return &result, nil
}

func (uc *UserClient) Delete(id int) error {
    resp, err := uc.client.R().
        SetPathParam("id", strconv.Itoa(id)).
        Delete("/users/:id")
    if err != nil {
        return err
    }
    defer resp.Close()
    
    return nil
}

// Usage
apiClient := NewAPIClient("https://api.example.com/v1", "your-api-key")
users := NewUserClient(apiClient)

// Create
user, err := users.Create(User{Name: "John", Email: "[email protected]"})

// Get
user, err = users.Get(123)

// List
result, err := users.List(UserListParams{Page: 1, PageSize: 10})

// Update
user, err = users.Update(123, User{Name: "Jane"})

// Delete
err = users.Delete(123)

Nested Resources

Handle nested API resources:
type Comment struct {
    ID     int    `json:"id"`
    PostID int    `json:"post_id"`
    Text   string `json:"text"`
}

// Get comments for a post
func GetPostComments(c *client.Client, postID int) ([]Comment, error) {
    resp, err := c.R().
        SetPathParam("postId", strconv.Itoa(postID)).
        Get("/posts/:postId/comments")
    if err != nil {
        return nil, err
    }
    defer resp.Close()
    
    var comments []Comment
    if err := resp.JSON(&comments); err != nil {
        return nil, err
    }
    
    return comments, nil
}

// Create a comment on a post
func CreatePostComment(c *client.Client, postID int, text string) (*Comment, error) {
    comment := Comment{Text: text}
    
    resp, err := c.R().
        SetPathParam("postId", strconv.Itoa(postID)).
        SetJSON(comment).
        Post("/posts/:postId/comments")
    if err != nil {
        return nil, err
    }
    defer resp.Close()
    
    var result Comment
    if err := resp.JSON(&result); err != nil {
        return nil, err
    }
    
    return &result, nil
}

Error Handling

Handle API errors consistently:
type APIError struct {
    Status  int    `json:"status"`
    Code    string `json:"code"`
    Message string `json:"message"`
}

func (e *APIError) Error() string {
    return fmt.Sprintf("API error %d (%s): %s", e.Status, e.Code, e.Message)
}

func HandleAPIResponse(resp *client.Response) error {
    if resp.StatusCode() >= 200 && resp.StatusCode() < 300 {
        return nil
    }
    
    var apiErr APIError
    if err := resp.JSON(&apiErr); err != nil {
        return fmt.Errorf("HTTP %d: %s", resp.StatusCode(), resp.Status())
    }
    
    apiErr.Status = resp.StatusCode()
    return &apiErr
}

// Usage in a resource method
func (uc *UserClient) Get(id int) (*User, error) {
    resp, err := uc.client.R().
        SetPathParam("id", strconv.Itoa(id)).
        Get("/users/:id")
    if err != nil {
        return nil, err
    }
    defer resp.Close()
    
    if err := HandleAPIResponse(resp); err != nil {
        return nil, err
    }
    
    var user User
    if err := resp.JSON(&user); err != nil {
        return nil, err
    }
    
    return &user, nil
}

Filtering and Searching

Implement search and filter functionality:
type UserSearchParams struct {
    Query    string `param:"q"`
    Role     string `param:"role"`
    Active   *bool  `param:"active"`
    Page     int    `param:"page"`
    PageSize int    `param:"page_size"`
}

func SearchUsers(c *client.Client, params UserSearchParams) ([]User, error) {
    resp, err := c.R().
        SetParamsWithStruct(params).
        Get("/users/search")
    if err != nil {
        return nil, err
    }
    defer resp.Close()
    
    var users []User
    if err := resp.JSON(&users); err != nil {
        return nil, err
    }
    
    return users, nil
}

// Usage
active := true
params := UserSearchParams{
    Query:    "john",
    Role:     "admin",
    Active:   &active,
    Page:     1,
    PageSize: 20,
}

users, err := SearchUsers(c, params)

Batch Operations

Perform operations on multiple resources:
type BatchCreateRequest struct {
    Users []User `json:"users"`
}

type BatchCreateResponse struct {
    Created []User `json:"created"`
    Errors  []struct {
        Index   int    `json:"index"`
        Message string `json:"message"`
    } `json:"errors"`
}

func BatchCreateUsers(c *client.Client, users []User) (*BatchCreateResponse, error) {
    req := BatchCreateRequest{Users: users}
    
    resp, err := c.R().
        SetJSON(req).
        Post("/users/batch")
    if err != nil {
        return nil, err
    }
    defer resp.Close()
    
    var result BatchCreateResponse
    if err := resp.JSON(&result); err != nil {
        return nil, err
    }
    
    return &result, nil
}

Next Steps

Examples

See complete working examples

Overview

Return to client overview

Build docs developers (and LLMs) love