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