π₯‘ 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 the 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.
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.
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")
}