Skip to main content
Version: Next

🧩 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")
}