fiber/docs/api/state.md
Manuel de la Peña 80db4de1a5
🔥 feat: Add Support for service dependencies (#3434)
* feat: support for starting devtime dependencies in an abstract manner

* feat: support for starting devtime dependencies in an abstract manner

* fix: spell

* fix: lint

* fix: markdown lint

* fix: b.Helper

* fix: lint spell

* fix: field padding

* chore: protect the usage of dev dependencies with the "dev" build tag

* fix: error message

* docs: fix type name

* fix: mock context cancellation

* docs: simpler

* fix: lint unused receiver

* fix: handle error in benchmarks

* lint: remove build tag

* fix: wrap error

* fix: lint

* fix: explain why lint exclusion

* chore: best effort while terminating dependencies

* gix: lintern name

* fix: reduce flakiness in tests

* chore: get dependency state for logs

* chore: protect dev time tests and benchmarks under build tag

* chore: add build tag in more places

* fix: more conservative context cancellation timeout in tests

* chore: remove build tags

* chore: rename to Services

* fix: update tests

* fix: lint

* fix: lint

* fix: apply coderrabit suggestion

* chore: add more unit tests

* chore: add more unit tests

* chore: refactor tests

* fix: avoid control flags in tests

* chore: consistent error message in start

* chore: simplify error logic

* chore: remove flag coupling

* chore: simplify benchmarks

* chore: add corerabbit suggetion

* fix: wording

* chore: log error on service termination

* docs: wording

* fix: typo in error message

* fix: wording

* fix: panic on startup error

* chore: store started services separately, so that we can terminate them properly

* docs: update example

* fix: use context provider instead of storing the context

* chore: use require.Empty

* fix: no tabs in docs

* chore: move field for better alignment

* docs: do not use interface as method receiver

* docs: proper usage of JSON bind

* fix: use startup context for bootstrap log

* chore: move happy path to the left

* fix: use configured consistently

* chore: terminate started services in reverse order

* fix: consistent access to the config context

* chore: test names and benchmarks location

* chore: benchmark refinement

* chore: store the services into the global State

* chore: add functions to access the Services in the state

* chore: hex-encode the hashes

* chore: consistent var name for services

* chore: non racey service initialisation

* fix: wrong range iteration in service keys

* fix: use inline

* chore: more tests for the generics functions for services

* chore: add benchmarks for service functions

* fix: benchmarks refactor was wrong

* fix. refine error message

* fix: do not cause overhead in newState, instead pre-calculate the prefix hash at init

* chore: simplify hashing

* chore: use smaller, and testable function for initServices

* chore: initialize services in the app.init

* chore: init services before blocking the app init

* Revert "chore: init services before blocking the app init"

This reverts commit bb67cf6380cb71ad5ae4ab4807cdfbf0c7eafa1b.

* chore: move happy path to the left at initServices

* fix: register shutdown hooks for services after app's mutext is unlocked

---------

Co-authored-by: Juan Calderon-Perez <835733+gaby@users.noreply.github.com>
2025-05-19 14:35:13 +02:00

673 lines
14 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
id: state
title: 🗂️ State Management
sidebar_position: 8
---
The State Management provides a global keyvalue store for managing application dependencies and runtime data. This store is shared across the entire application and remains consistent between requests, and it's used to store [Services](../api/services). You can retrieve those Services from the global state using the `GetService` or `MustGetService` functions.
## State Type
`State` is a keyvalue store built on top of `sync.Map` to ensure safe concurrent access. It allows storage and retrieval of dependencies and configurations in a Fiber application as well as threadsafe access to runtime data.
### Definition
```go
// State is a keyvalue store for Fiber's app, used as a global storage for the app's dependencies.
// It is a threadsafe implementation of a map[string]any, using sync.Map.
type State struct {
dependencies sync.Map
}
```
## Methods on State
### Set
Set adds or updates a keyvalue pair in the State.
```go
// Set adds or updates a keyvalue pair in the State.
func (s *State) Set(key string, value any)
```
**Usage Example:**
```go
app.State().Set("appName", "My Fiber App")
```
### Get
Get retrieves a value from the State.
```go title="Signature"
func (s *State) Get(key string) (any, bool)
```
**Usage Example:**
```go
value, ok := app.State().Get("appName")
if ok {
fmt.Println("App Name:", value)
}
```
### MustGet
MustGet retrieves a value from the State and panics if the key is not found.
```go title="Signature"
func (s *State) MustGet(key string) any
```
**Usage Example:**
```go
appName := app.State().MustGet("appName")
fmt.Println("App Name:", appName)
```
### Has
Has checks if a key exists in the State.
```go title="Signature"s
func (s *State) Has(key string) bool
```
**Usage Example:**
```go
if app.State().Has("appName") {
fmt.Println("App Name is set.")
}
```
### Delete
Delete removes a keyvalue pair from the State.
```go title="Signature"
func (s *State) Delete(key string)
```
**Usage Example:**
```go
app.State().Delete("obsoleteKey")
```
### Reset
Reset removes all keys from the State, including those related to Services.
```go title="Signature"
func (s *State) Reset()
```
**Usage Example:**
```go
app.State().Reset()
```
### Keys
Keys returns a slice containing all keys present in the State.
```go title="Signature"
func (s *State) Keys() []string
```
**Usage Example:**
```go
keys := app.State().Keys()
fmt.Println("State Keys:", keys)
```
### Len
Len returns the number of keys in the State.
```go
// Len returns the number of keys in the State.
func (s *State) Len() int
```
**Usage Example:**
```go
fmt.Printf("Total State Entries: %d\n", app.State().Len())
```
### GetString
GetString retrieves a string value from the State. It returns the string and a boolean indicating a successful type assertion.
```go title="Signature"
func (s *State) GetString(key string) (string, bool)
```
**Usage Example:**
```go
if appName, ok := app.State().GetString("appName"); ok {
fmt.Println("App Name:", appName)
}
```
### GetInt
GetInt retrieves an integer value from the State. It returns the int and a boolean indicating a successful type assertion.
```go title="Signature"
func (s *State) GetInt(key string) (int, bool)
```
**Usage Example:**
```go
if count, ok := app.State().GetInt("userCount"); ok {
fmt.Printf("User Count: %d\n", count)
}
```
### GetBool
GetBool retrieves a boolean value from the State. It returns the bool and a boolean indicating a successful type assertion.
```go title="Signature"
func (s *State) GetBool(key string) (value, bool)
```
**Usage Example:**
```go
if debug, ok := app.State().GetBool("debugMode"); ok {
fmt.Printf("Debug Mode: %v\n", debug)
}
```
### GetFloat64
GetFloat64 retrieves a float64 value from the State. It returns the float64 and a boolean indicating a successful type assertion.
```go title="Signature"
func (s *State) GetFloat64(key string) (float64, bool)
```
**Usage Example:**
```go title="Signature"
if ratio, ok := app.State().GetFloat64("scalingFactor"); ok {
fmt.Printf("Scaling Factor: %f\n", ratio)
}
```
### GetUint
GetUint retrieves a `uint` value from the State.
```go title="Signature"
func (s *State) GetUint(key string) (uint, bool)
```
**Usage Example:**
```go
if val, ok := app.State().GetUint("maxConnections"); ok {
fmt.Printf("Max Connections: %d\n", val)
}
```
### GetInt8
GetInt8 retrieves an `int8` value from the State.
```go title="Signature"
func (s *State) GetInt8(key string) (int8, bool)
```
**Usage Example:**
```go
if val, ok := app.State().GetInt8("threshold"); ok {
fmt.Printf("Threshold: %d\n", val)
}
```
### GetInt16
GetInt16 retrieves an `int16` value from the State.
```go title="Signature"
func (s *State) GetInt16(key string) (int16, bool)
```
**Usage Example:**
```go
if val, ok := app.State().GetInt16("minValue"); ok {
fmt.Printf("Minimum Value: %d\n", val)
}
```
### GetInt32
GetInt32 retrieves an `int32` value from the State.
```go title="Signature"
func (s *State) GetInt32(key string) (int32, bool)
```
**Usage Example:**
```go
if val, ok := app.State().GetInt32("portNumber"); ok {
fmt.Printf("Port Number: %d\n", val)
}
```
### GetInt64
GetInt64 retrieves an `int64` value from the State.
```go title="Signature"
func (s *State) GetInt64(key string) (int64, bool)
```
**Usage Example:**
```go
if val, ok := app.State().GetInt64("fileSize"); ok {
fmt.Printf("File Size: %d\n", val)
}
```
### GetUint8
GetUint8 retrieves a `uint8` value from the State.
```go title="Signature"
func (s *State) GetUint8(key string) (uint8, bool)
```
**Usage Example:**
```go
if val, ok := app.State().GetUint8("byteValue"); ok {
fmt.Printf("Byte Value: %d\n", val)
}
```
### GetUint16
GetUint16 retrieves a `uint16` value from the State.
```go title="Signature"
func (s *State) GetUint16(key string) (uint16, bool)
```
**Usage Example:**
```go
if val, ok := app.State().GetUint16("limit"); ok {
fmt.Printf("Limit: %d\n", val)
}
```
### GetUint32
GetUint32 retrieves a `uint32` value from the State.
```go title="Signature"
func (s *State) GetUint32(key string) (uint32, bool)
```
**Usage Example:**
```go
if val, ok := app.State().GetUint32("timeout"); ok {
fmt.Printf("Timeout: %d\n", val)
}
```
### GetUint64
GetUint64 retrieves a `uint64` value from the State.
```go title="Signature"
func (s *State) GetUint64(key string) (uint64, bool)
```
**Usage Example:**
```go
if val, ok := app.State().GetUint64("maxSize"); ok {
fmt.Printf("Max Size: %d\n", val)
}
```
### GetUintptr
GetUintptr retrieves a `uintptr` value from the State.
```go title="Signature"
func (s *State) GetUintptr(key string) (uintptr, bool)
```
**Usage Example:**
```go
if val, ok := app.State().GetUintptr("pointerValue"); ok {
fmt.Printf("Pointer Value: %d\n", val)
}
```
### GetFloat32
GetFloat32 retrieves a `float32` value from the State.
```go title="Signature"
func (s *State) GetFloat32(key string) (float32, bool)
```
**Usage Example:**
```go
if val, ok := app.State().GetFloat32("scalingFactor32"); ok {
fmt.Printf("Scaling Factor (float32): %f\n", val)
}
```
### GetComplex64
GetComplex64 retrieves a `complex64` value from the State.
```go title="Signature"
func (s *State) GetComplex64(key string) (complex64, bool)
```
**Usage Example:**
```go
if val, ok := app.State().GetComplex64("complexVal"); ok {
fmt.Printf("Complex Value (complex64): %v\n", val)
}
```
### GetComplex128
GetComplex128 retrieves a `complex128` value from the State.
```go title="Signature"
func (s *State) GetComplex128(key string) (complex128, bool)
```
**Usage Example:**
```go
if val, ok := app.State().GetComplex128("complexVal128"); ok {
fmt.Printf("Complex Value (complex128): %v\n", val)
}
```
## Generic Functions
Fiber provides generic functions to retrieve state values with type safety and fallback options.
### GetState
GetState retrieves a value from the State and casts it to the desired type. It returns the cast value and a boolean indicating if the cast was successful.
```go title="Signature"
func GetState[T any](s *State, key string) (T, bool)
```
**Usage Example:**
```go
// Retrieve an integer value safely.
userCount, ok := GetState[int](app.State(), "userCount")
if ok {
fmt.Printf("User Count: %d\n", userCount)
}
```
### MustGetState
MustGetState retrieves a value from the State and casts it to the desired type. It panics if the key is not found or if the type assertion fails.
```go title="Signature"
func MustGetState[T any](s *State, key string) T
```
**Usage Example:**
```go
// Retrieve the value or panic if it is not present.
config := MustGetState[string](app.State(), "configFile")
fmt.Println("Config File:", config)
```
### GetStateWithDefault
GetStateWithDefault retrieves a value from the State, casting it to the desired type. If the key is not present, it returns the provided default value.
```go title="Signature"
func GetStateWithDefault[T any](s *State, key string, defaultVal T) T
```
**Usage Example:**
```go
// Retrieve a value with a default fallback.
requestCount := GetStateWithDefault[int](app.State(), "requestCount", 0)
fmt.Printf("Request Count: %d\n", requestCount)
```
### GetService
GetService retrieves a Service from the State and casts it to the desired type. It returns the cast value and a boolean indicating if the cast was successful.
```go title="Signature"
func GetService[T Service](s *State, key string) (T, bool) {
```
**Usage Example:**
```go
if srv, ok := fiber.GetService[*redisService](app.State(), "someService")
fmt.Printf("Some Service: %s\n", srv.String())
}
```
### MustGetService
MustGetService retrieves a Service from the State and casts it to the desired type. It panics if the key is not found or if the type assertion fails.
```go title="Signature"
func MustGetService[T Service](s *State, key string) T
```
**Usage Example:**
```go
srv := fiber.MustGetService[*SomeService](app.State(), "someService")
```
## Comprehensive Examples
### Example: Request Counter
This example demonstrates how to track the number of requests using the State.
```go
package main
import (
"fmt"
"github.com/gofiber/fiber/v3"
)
func main() {
app := fiber.New()
// Initialize state with a counter.
app.State().Set("requestCount", 0)
// Middleware: Increase counter for every request.
app.Use(func(c fiber.Ctx) error {
count, _ := c.App().State().GetInt("requestCount")
app.State().Set("requestCount", count+1)
return c.Next()
})
app.Get("/", func(c fiber.Ctx) error {
return c.SendString("Hello World!")
})
app.Get("/stats", func(c fiber.Ctx) error {
count, _ := c.App().State().Get("requestCount")
return c.SendString(fmt.Sprintf("Total requests: %d", count))
})
app.Listen(":3000")
}
```
### Example: EnvironmentSpecific Configuration
This example shows how to configure different settings based on the environment.
```go
package main
import (
"os"
"github.com/gofiber/fiber/v3"
)
func main() {
app := fiber.New()
// Determine environment.
environment := os.Getenv("ENV")
if environment == "" {
environment = "development"
}
app.State().Set("environment", environment)
// Set environment-specific configurations.
if environment == "development" {
app.State().Set("apiUrl", "http://localhost:8080/api")
app.State().Set("debug", true)
} else {
app.State().Set("apiUrl", "https://api.production.com")
app.State().Set("debug", false)
}
app.Get("/config", func(c fiber.Ctx) error {
config := map[string]any{
"environment": environment,
"apiUrl": fiber.GetStateWithDefault(c.App().State(), "apiUrl", ""),
"debug": fiber.GetStateWithDefault(c.App().State(), "debug", false),
}
return c.JSON(config)
})
app.Listen(":3000")
}
```
### Example: Dependency Injection with State Management
This example demonstrates how to use the State for dependency injection in a Fiber application.
```go
package main
import (
"context"
"fmt"
"log"
"github.com/gofiber/fiber/v3"
"github.com/redis/go-redis/v9"
)
type User struct {
ID int `query:"id"`
Name string `query:"name"`
Email string `query:"email"`
}
func main() {
app := fiber.New()
ctx := context.Background()
// Initialize Redis client.
rdb := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
Password: "",
DB: 0,
})
// Check the Redis connection.
if err := rdb.Ping(ctx).Err(); err != nil {
log.Fatalf("Could not connect to Redis: %v", err)
}
// Inject the Redis client into Fiber's State for dependency injection.
app.State().Set("redis", rdb)
app.Get("/user/create", func(c fiber.Ctx) error {
var user User
if err := c.Bind().Query(&user); err != nil {
return c.Status(fiber.StatusBadRequest).SendString(err.Error())
}
// Save the user to the database.
rdb, ok := fiber.GetState[*redis.Client](c.App().State(), "redis")
if !ok {
return c.Status(fiber.StatusInternalServerError).SendString("Redis client not found")
}
// Save the user to the database.
key := fmt.Sprintf("user:%d", user.ID)
err := rdb.HSet(ctx, key, "name", user.Name, "email", user.Email).Err()
if err != nil {
return c.Status(fiber.StatusInternalServerError).SendString(err.Error())
}
return c.JSON(user)
})
app.Get("/user/:id", func(c fiber.Ctx) error {
id := c.Params("id")
rdb, ok := fiber.GetState[*redis.Client](c.App().State(), "redis")
if !ok {
return c.Status(fiber.StatusInternalServerError).SendString("Redis client not found")
}
key := fmt.Sprintf("user:%s", id)
user, err := rdb.HGetAll(ctx, key).Result()
if err == redis.Nil {
return c.Status(fiber.StatusNotFound).SendString("User not found")
} else if err != nil {
return c.Status(fiber.StatusInternalServerError).SendString(err.Error())
}
return c.JSON(user)
})
app.Listen(":3000")
}
```