A lightweight and flexible Dependency Injection Container for Lua.
This library allows you to register and manage your application’s dependencies in a centralized way, supporting singletons, lazy singletons, and factories with automatic dependency resolution.
- Simple API:
put
,put_lazy
,create
,find
,remove
,clear
- Automatic dependency resolution based on registered keys
- Singletons (immediate and lazy)
- Factories (new instance each time)
- Cycle detection (prevents infinite recursion)
- Centralized container with global or scoped usage
- Perfect for modular projects and testable code
Installation from luarocks:
luarocks install lua-injection
Then require it in your code:
local injection = require("injection")
Creates and returns a new isolated container instance. All registrations live inside this container.
local container = injection.create_container()
Registers a singleton service, instantiated immediately.
name
: unique key for the servicebuilder
: function returning the instancedeps
(optional): list of dependency keys to be resolved and passed into the builder
container.put("logger", function()
return Logger:new()
end)
container.put("db", function()
return Database:new("connection_string")
end)
-- With dependencies
container.put("userRepo", function(db, logger)
return UserRepository:new(db, logger)
end, { "db", "logger" })
Registers a lazy singleton service, instantiated only when first resolved via find
.
container.put_lazy("authService", function(userRepo, logger)
return AuthService:new(userRepo, logger)
end, { "userRepo", "logger" })
Registers a factory service.
Each call to find
returns a new instance.
container.create("userController", function(authService, logger)
return UserController:new(authService, logger)
end, { "authService", "logger" })
Resolves and returns the instance for a given key.
- For singletons → always returns the same instance
- For factories → creates a new instance every call
- Dependencies are automatically resolved
local controller1 = container.find("userController")
local controller2 = container.find("userController")
print(controller1 ~= controller2) -- true (factory creates a new instance)
Removes a specific service from the container.
container.remove("db")
Removes all registered services from the container.
container.clear()
Imagine a project with multiple services:
-- Services
local Logger = require("Logger")
local Database = require("Database")
local UserRepository = require("UserRepository")
local AuthService = require("AuthService")
local UserController = require("UserController")
-- Create container
local container = injection.create_container()
-- Register
container.put("logger", function() return Logger:new() end)
container.put("db", function() return Database:new() end)
container.put("userRepo", function(db, logger) return UserRepository:new(db, logger) end, { "db", "logger" })
container.put_lazy("authService", function(userRepo, logger) return AuthService:new(userRepo, logger) end, { "userRepo", "logger" })
container.create("userController", function(authService, logger) return UserController:new(authService, logger) end, { "authService", "logger" })
-- Usage
local controller = container.find("userController")
controller:login(123, "secret")
-
If a builder is not a function:
Error: Builder for 'X' must be a function
-
If a dependency cannot be found:
Error: Failed to resolve dependency: 'Y' not found
-
If a cycle is detected:
Error: Cyclic dependency detected: X
- Centralized dependency management
- Testable code: easily swap services (e.g., real DB ↔ mock DB)
- Avoids hard-coded requires everywhere
- Supports both global and local scopes via
create_container()
MIT License. Feel free to use it in your projects.