Creating Modules in Lua
Creating well-designed modules is essential for building maintainable Lua applications. In this lesson, you'll learn advanced module creation techniques including proper exports, private members, initialization patterns, and professional module design. Let's explore how to create robust, reusable modules!
Module Creation Patterns
Basic Module Pattern
-- mymodule.lua
local M = {}
function M.func1()
return "Function 1"
end
function M.func2()
return "Function 2"
end
return M
Module with Private Functions
-- validator.lua
local M = {}
-- Private helper function
local function isString(value)
return type(value) == "string"
end
-- Private helper function
local function isNumber(value)
return type(value) == "number"
end
-- Public function using private helpers
function M.validateEmail(email)
if not isString(email) then
return false, "Email must be a string"
end
if not email:match("^[%w.]+@[%w.]+%.%w+$") then
return false, "Invalid email format"
end
return true
end
function M.validateAge(age)
if not isNumber(age) then
return false, "Age must be a number"
end
if age < 0 or age > 150 then
return false, "Age must be between 0 and 150"
end
return true
end
return M
Click Run to execute your code
Module Exports
Explicit Exports
-- math_utils.lua
local M = {}
local function square(x)
return x * x
end
local function cube(x)
return x * x * x
end
-- Explicitly export functions
M.square = square
M.cube = cube
-- Or define directly
M.add = function(a, b)
return a + b
end
return M
Selective Exports
-- database.lua
local M = {}
-- Internal state (not exported)
local connection = nil
local isConnected = false
-- Private function
local function validateConnection()
if not isConnected then
error("Not connected to database")
end
end
-- Public API
function M.connect(host, port)
connection = {host = host, port = port}
isConnected = true
return true
end
function M.disconnect()
connection = nil
isConnected = false
end
function M.query(sql)
validateConnection()
-- Execute query
return "Query result"
end
-- Export only what's needed
return M
Click Run to execute your code
Module Initialization
Initialization on Load
-- config.lua
local M = {}
-- Initialize on module load
local defaults = {
debug = false,
timeout = 30,
retries = 3
}
local settings = {}
-- Copy defaults
for k, v in pairs(defaults) do
settings[k] = v
end
function M.set(key, value)
settings[key] = value
end
function M.get(key)
return settings[key]
end
function M.reset()
for k, v in pairs(defaults) do
settings[k] = v
end
end
return M
Lazy Initialization
-- cache.lua
local M = {}
local cache = nil
local function initCache()
if not cache then
cache = {}
print("Cache initialized")
end
end
function M.set(key, value)
initCache()
cache[key] = value
end
function M.get(key)
initCache()
return cache[key]
end
return M
Module Constants and Metadata
-- http.lua
local M = {}
-- Constants
M.VERSION = "1.0.0"
M.AUTHOR = "Your Name"
M.STATUS_CODES = {
OK = 200,
NOT_FOUND = 404,
SERVER_ERROR = 500
}
M.METHODS = {
GET = "GET",
POST = "POST",
PUT = "PUT",
DELETE = "DELETE"
}
-- Functions
function M.request(method, url)
if not M.METHODS[method] then
error("Invalid HTTP method")
end
-- Make request
return {status = M.STATUS_CODES.OK}
end
return M
Click Run to execute your code
Module Documentation
--[[
String Utilities Module
Provides common string manipulation functions.
@module string_utils
@author Your Name
@version 1.0.0
@license MIT
]]
local M = {}
--[[
Trims whitespace from both ends of a string
@param s string The string to trim
@return string The trimmed string
@usage local result = string_utils.trim(" hello ")
]]
function M.trim(s)
return s:match("^%s*(.-)%s*$")
end
--[[
Splits a string by a delimiter
@param s string The string to split
@param delimiter string The delimiter to split by
@return table Array of string parts
@usage local parts = string_utils.split("a,b,c", ",")
]]
function M.split(s, delimiter)
local result = {}
local pattern = string.format("([^%s]+)", delimiter)
for match in s:gmatch(pattern) do
table.insert(result, match)
end
return result
end
return M
Practical Module Examples
Event Emitter Module
-- event_emitter.lua
local M = {}
function M.new()
local self = {
listeners = {}
}
function self:on(event, callback)
if not self.listeners[event] then
self.listeners[event] = {}
end
table.insert(self.listeners[event], callback)
end
function self:emit(event, ...)
if self.listeners[event] then
for i, callback in ipairs(self.listeners[event]) do
callback(...)
end
end
end
function self:off(event, callback)
if self.listeners[event] then
for i, cb in ipairs(self.listeners[event]) do
if cb == callback then
table.remove(self.listeners[event], i)
break
end
end
end
end
return self
end
return M
Validation Module
-- validation.lua
local M = {}
local validators = {}
function validators.required(value)
if value == nil or value == "" then
return false, "This field is required"
end
return true
end
function validators.email(value)
if not value:match("^[%w.]+@[%w.]+%.%w+$") then
return false, "Invalid email format"
end
return true
end
function validators.minLength(min)
return function(value)
if #value < min then
return false, "Minimum length is " .. min
end
return true
end
end
function validators.maxLength(max)
return function(value)
if #value > max then
return false, "Maximum length is " .. max
end
return true
end
end
function M.validate(value, rules)
for i, rule in ipairs(rules) do
local valid, error = rule(value)
if not valid then
return false, error
end
end
return true
end
M.validators = validators
return M
Click Run to execute your code
Module Best Practices
- Use local for everything: Avoid global variables
- Return a table: Always return the module table
- Keep it focused: One module, one responsibility
- Document your API: Add comments for public functions
- Version your modules: Include version information
- Hide implementation: Keep private functions local
- Avoid side effects: Don't modify global state
- Test your modules: Write tests for public API
Practice Exercise
Try creating these modules:
Click Run to execute your code
Summary
In this lesson, you learned:
- Advanced module creation patterns
- Explicit and selective exports
- Module initialization strategies
- Adding constants and metadata
- Documenting modules properly
- Practical examples: EventEmitter, Validation
- Module best practices
What's Next?
Now that you can create professional modules, it's time to learn error handling! In the next lesson, you'll explore how to handle errors gracefully using pcall, xpcall, and custom error handling patterns. Let's continue! ๐
Enjoying these tutorials?