Web Analytics

Creating Modules in Lua

Intermediate ~25 min read

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
Output
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
Output
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
Output
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
Output
Click Run to execute your code

Module Best Practices

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:

Output
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! ๐Ÿš€