Skip to content

Services

Services is a Service Locator (sometimes called inversion of control) framework for Vapor. The services framework allows you to register, configure, and initialize anything you might need in your application.

Container

Most of your interaction with services will happen through a container. A container is a combination of the following:

  • Services: A collection of registered services.
  • Config: Declared preferences for certain services over others.
  • Environment: The application's current environment type (testing, production, etc)
  • Worker: The event loop associated with this container.

The most common containers you will interact with in Vapor are:

  • Application
  • Request
  • Response

You should use the Application as a container to create services required for booting your app. You should use the Request or Response containers to create services for responding to requests (in route closures and controllers).

Make

Making services is simple, just call .make(_:) on a container and pass the type you want, usually a protocol like Client.

let client = try req.make(Client.self)

You can also specify a concrete type if you know exactly what you want.

let leaf = try req.make(LeafRenderer.self)
print(leaf) /// Definitely a LeafRenderer

let view = try req.make(ViewRenderer.self)
print(view) /// ViewRenderer, might be a LeafRenderer

Tip

Try to rely on protocols over concrete types if you can. This will make testing your code easier (you can easily swap in dummy implementations) and it can help keep your code decoupled.

Services

The Services struct contains all of the services you—or the service providers you have added—have registered. You will usually register and configure your services in configure.swift.

Instance

You can register initialized service instances using .register(_:).

/// Create an in-memory SQLite database
let sqlite = SQLiteDatabase(storage: .memory)

/// Register to sevices.
services.register(sqlite)

After you register a service, it will be available for creation by a Container.

let db = app.make(SQLiteDatabase.self)
print(db) // SQLiteDatabase (the one we registered earlier)

Protocol

When registering services, you can also declare conformance to a particular protocol. You might have noticed that this is how Vapor registers its main router.

/// Register routes to the router
let router = EngineRouter.default()
try routes(router)
services.register(router, as: Router.self)

Since we register the router variable with as: Router.self, it can be created using either the concrete type or the protocol.

let router = app.make(Router.self)
let engineRouter = app.make(EngineRouter.self)
print(router) // Router (actually EngineRouter)
print(engineRouter) // EngineRouter
print(router === engineRouter) // true

Environment

The environment is used to dynamically change how your Vapor app behaves in certain situations. For example, you probably want to use a different username and password for your database when your application is deployed. The Environment type makes managing this easy.

When you run your Vapor app from the command line, you can pass an optional --env flag to specify the environment. By default, the environment will be .development.

swift run Run --env prod

In the above example, we are running Vapor in the .production environment. This environment specifies isRelease = true.

You can use the environment passed into configure.swift to dynamically register services.

let sqlite: SQLiteDatabase
if env.isRelease {
    /// Create file-based SQLite db using $SQLITE_PATH from process env
    sqlite = try SQLiteDatabase(storage: .file(path: Environment.get("SQLITE_PATH")!))
} else {
    /// Create an in-memory SQLite database
    sqlite = try SQLiteDatabase(storage: .memory)
}
services.register(sqlite)

Info

Use the static method Environment.get(_:) to fetch string values from the process environment.

You can also dynamically register services based on environment using the factory .register(_:) method.

services.register { container -> BCryptConfig in
  let cost: Int

  switch container.environment {
  case .production: cost = 12
  default: cost = 4
  }

  return BCryptConfig(cost: cost)
}

Config

If multiple services are available for a given protocol, you will need to use the Config struct to declare which service you prefer.

ServiceError.ambiguity: Please choose which KeyedCache you prefer, multiple are available: MemoryKeyedCache, FluentCache<SQLiteDatabase>.

This is also done in configure.swift, just use the config.prefer(_:for:) method.

/// Declare preference for MemoryKeyedCache anytime a container is asked to create a KeyedCache
config.prefer(MemoryKeyedCache.self, for: KeyedCache.self)

/// ...

/// Create a KeyedCache using the Request container
let cache = req.make(KeyedCache.self)
print(cache is MemoryKeyedCache) // true