π§© Services
Services wrap external dependencies. Register them in the application's state, and Fiber starts and stops them automaticallyβuseful during development and testing.
After adding a service to the app configuration, Fiber starts it on launch and stops it during shutdown. Retrieve a service from state with GetService or MustGetService (see State Management).
Service Interfaceβ
The Service interface defines methods a service must implement.
Definitionβ
type Service interface {
// Start starts the service, returning an error if it fails.
Start(ctx context.Context) error
// String returns a string representation of the service.
// It is used to print a human-readable name of the service in the startup message.
String() string
// State returns the current state of the service.
State(ctx context.Context) (string, error)
// Terminate terminates the service, returning an error if it fails.
Terminate(ctx context.Context) error
}
Service Methodsβ
Startβ
Starts the service. Fiber calls this when the application starts.
func (s *SomeService) Start(ctx context.Context) error
Stringβ
Returns a string representation of the service, used to print the service in the startup message and as the key the service is registered under in the application state. Names must be unique across the configured services: fiber.New panics when two services report the same name, since one would otherwise overwrite the other and never be terminated.
func (s *SomeService) String() string
Stateβ
Reports the current state of the service for the startup message.
func (s *SomeService) State(ctx context.Context) (string, error)
Terminateβ
Stops the service after the application shuts down using a post-shutdown hook. Services are terminated in reverse start order, so a service can rely on the ones started before it still being available while it shuts down.
func (s *SomeService) Terminate(ctx context.Context) error
Comprehensive Examplesβ
Example: Adding a Serviceβ
This example demonstrates how to add a Redis store as a service to the application, backed by the Testcontainers Redis Go module.
package main
import (
"context"
"fmt"
"log"
"time"
"github.com/gofiber/fiber/v3"
"github.com/redis/go-redis/v9"
tcredis "github.com/testcontainers/testcontainers-go/modules/redis"
)
const redisServiceName = "redis-store"
type redisService struct {
ctr *tcredis.RedisContainer
}
// Start initializes and starts the service. It implements the [fiber.Service] interface.
func (s *redisService) Start(ctx context.Context) error {
// start the service
c, err := tcredis.Run(ctx, "redis:latest")
if err != nil {
return err
}
s.ctr = c
return nil
}
// String returns a string representation of the service.
// It is used to print a human-readable name of the service in the startup message.
// It implements the [fiber.Service] interface.
func (s *redisService) String() string {
return redisServiceName
}
// State returns the current state of the service.
// It implements the [fiber.Service] interface.
func (s *redisService) State(ctx context.Context) (string, error) {
state, err := s.ctr.State(ctx)
if err != nil {
return "", fmt.Errorf("container state: %w", err)
}
return state.Status, nil
}
// Terminate stops and removes the service. It implements the [fiber.Service] interface.
func (s *redisService) Terminate(ctx context.Context) error {
// stop the service
return s.ctr.Terminate(ctx)
}
func main() {
cfg := &fiber.Config{}
// Initialize service.
cfg.Services = append(cfg.Services, &redisService{})
// Define a context provider for the services startup.
// This is useful to cancel the startup of the services if the context is canceled.
// Default is context.Background().
startupCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
cfg.ServicesStartupContextProvider = func() context.Context {
return startupCtx
}
// Define a context provider for the services shutdown.
// This is useful to cancel the shutdown of the services if the context is canceled.
// Default is context.Background().
shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
cfg.ServicesShutdownContextProvider = func() context.Context {
return shutdownCtx
}
app := fiber.New(*cfg)
ctx := context.Background()
// Obtain the Redis service from the application's State.
redisSrv, ok := fiber.GetService[*redisService](app.State(), redisServiceName)
if !ok || redisSrv == nil {
log.Printf("Redis service not found")
return
}
// Obtain the connection string from the service.
connString, err := redisSrv.ctr.ConnectionString(ctx)
if err != nil {
log.Printf("Could not get connection string: %v", err)
return
}
// Parse the connection string to create a Redis client.
options, err := redis.ParseURL(connString)
if err != nil {
log.Printf("failed to parse connection string: %s", err)
return
}
// Initialize the Redis client.
rdb := redis.NewClient(options)
// Check the Redis connection.
if err := rdb.Ping(ctx).Err(); err != nil {
log.Fatalf("Could not connect to Redis: %v", err)
}
app.Listen(":3000")
}
Example: Add a service with the Store middlewareβ
This example shows how to use services with the Store middleware for dependency injection. It uses a Redis store backed by the Testcontainers Redis module.
package main
import (
"context"
"encoding/json"
"fmt"
"log"
"time"
"github.com/gofiber/fiber/v3"
"github.com/gofiber/fiber/v3/middleware/logger"
redisStore "github.com/gofiber/storage/redis/v3"
"github.com/redis/go-redis/v9"
tcredis "github.com/testcontainers/testcontainers-go/modules/redis"
)
const (
redisServiceName = "redis-store"
)
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
}
type redisService struct {
ctr *tcredis.RedisContainer
}
// Start initializes and starts the service. It implements the [fiber.Service] interface.
func (s *redisService) Start(ctx context.Context) error {
// start the service
c, err := tcredis.Run(ctx, "redis:latest")
if err != nil {
return err
}
s.ctr = c
return nil
}
// String returns a string representation of the service.
// It is used to print a human-readable name of the service in the startup message.
// It implements the [fiber.Service] interface.
func (s *redisService) String() string {
return redisServiceName
}
// State returns the current state of the service.
// It implements the [fiber.Service] interface.
func (s *redisService) State(ctx context.Context) (string, error) {
state, err := s.ctr.State(ctx)
if err != nil {
return "", fmt.Errorf("container state: %w", err)
}
return state.Status, nil
}
// Terminate stops and removes the service. It implements the [fiber.Service] interface.
func (s *redisService) Terminate(ctx context.Context) error {
// stop the service
return s.ctr.Terminate(ctx)
}
func main() {
cfg := &fiber.Config{}
// Initialize service.
cfg.Services = append(cfg.Services, &redisService{})
// Define a context provider for the services startup.
// This is useful to cancel the startup of the services if the context is canceled.
// Default is context.Background().
startupCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
cfg.ServicesStartupContextProvider = func() context.Context {
return startupCtx
}
// Define a context provider for the services shutdown.
// This is useful to cancel the shutdown of the services if the context is canceled.
// Default is context.Background().
shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
cfg.ServicesShutdownContextProvider = func() context.Context {
return shutdownCtx
}
app := fiber.New(*cfg)
// Initialize default config
app.Use(logger.New())
ctx := context.Background()
// Obtain the Redis service from the application's State.
redisSrv, ok := fiber.GetService[*redisService](app.State(), redisServiceName)
if !ok || redisSrv == nil {
log.Printf("Redis service not found")
return
}
// Obtain the connection string from the service.
connString, err := redisSrv.ctr.ConnectionString(ctx)
if err != nil {
log.Printf("Could not get connection string: %v", err)
return
}
// define a GoFiber session store, backed by the Redis service
store := redisStore.New(redisStore.Config{
URL: connString,
})
app.Post("/user/create", func(c fiber.Ctx) error {
var user User
if err := c.Bind().JSON(&user); err != nil {
return c.Status(fiber.StatusBadRequest).SendString(err.Error())
}
json, err := json.Marshal(user)
if err != nil {
return c.Status(fiber.StatusInternalServerError).SendString(err.Error())
}
// Save the user to the database.
err = store.Set(user.Email, json, time.Hour*24)
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")
user, err := store.Get(id)
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(string(user))
})
app.Listen(":3000")
}