Web Analytics

Modules in Lua

Intermediate ~30 min read

Modules are the key to organizing and reusing code in Lua. They allow you to split your code into separate files, create libraries, and manage dependencies. Lua's module system is simple yet powerful, using tables and the require() function. In this lesson, you'll learn how to create, use, and organize modules effectively. Let's dive in!

What are Modules?

A module is simply a Lua file that returns a table containing functions and values:

-- mymodule.lua
local M = {}

function M.greet(name)
    return "Hello, " .. name
end

function M.add(a, b)
    return a + b
end

M.version = "1.0"

return M
Module Structure:
  1. Create a local table (usually named M or the module name)
  2. Add functions and values to the table
  3. Return the table at the end

Using require()

Load modules with the require() function:

-- main.lua
local mymodule = require("mymodule")

print(mymodule.greet("Alice"))  -- Hello, Alice
print(mymodule.add(5, 3))       -- 8
print(mymodule.version)         -- 1.0
Tip: require() caches modules. If you require the same module multiple times, it only loads once and returns the cached version.
Output
Click Run to execute your code

Module Patterns

1. Basic Table Module

-- math_utils.lua
local M = {}

function M.square(x)
    return x * x
end

function M.cube(x)
    return x * x * x
end

return M

2. Module with Private Functions

-- calculator.lua
local M = {}

-- Private function (not exported)
local function validate(x)
    return type(x) == "number"
end

-- Public functions
function M.add(a, b)
    if not (validate(a) and validate(b)) then
        error("Arguments must be numbers")
    end
    return a + b
end

function M.multiply(a, b)
    if not (validate(a) and validate(b)) then
        error("Arguments must be numbers")
    end
    return a * b
end

return M

3. Module with State

-- counter.lua
local M = {}
local count = 0  -- Private state

function M.increment()
    count = count + 1
    return count
end

function M.decrement()
    count = count - 1
    return count
end

function M.get()
    return count
end

function M.reset()
    count = 0
end

return M
Output
Click Run to execute your code

Class Modules

Modules can export classes:

-- person.lua
local Person = {}
Person.__index = Person

function Person:new(name, age)
    local self = setmetatable({}, Person)
    self.name = name
    self.age = age
    return self
end

function Person:greet()
    return "Hello, I'm " .. self.name
end

function Person:birthday()
    self.age = self.age + 1
end

return Person
-- main.lua
local Person = require("person")

local alice = Person:new("Alice", 25)
print(alice:greet())  -- Hello, I'm Alice
alice:birthday()
print(alice.age)  -- 26

Module Organization

Directory Structure

myapp/
├── main.lua
├── lib/
│   ├── utils.lua
│   ├── database.lua
│   └── models/
│       ├── user.lua
│       └── product.lua
└── config.lua

Loading from Subdirectories

-- Use dot notation for subdirectories
local User = require("lib.models.user")
local utils = require("lib.utils")
Important: Lua searches for modules in paths specified by package.path. Use forward slashes or dots for subdirectories, not backslashes.

Practical Examples

String Utilities Module

-- string_utils.lua
local M = {}

function M.trim(s)
    return s:match("^%s*(.-)%s*$")
end

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

function M.capitalize(s)
    return s:sub(1, 1):upper() .. s:sub(2):lower()
end

function M.reverse(s)
    return s:reverse()
end

return M

Configuration Module

-- config.lua
local M = {}

M.app = {
    name = "MyApp",
    version = "1.0.0",
    debug = true
}

M.database = {
    host = "localhost",
    port = 5432,
    name = "mydb"
}

M.server = {
    host = "0.0.0.0",
    port = 8080
}

function M.get(key)
    local keys = {}
    for k in key:gmatch("[^.]+") do
        table.insert(keys, k)
    end
    
    local value = M
    for i, k in ipairs(keys) do
        value = value[k]
        if not value then return nil end
    end
    return value
end

return M

Logger Module

-- logger.lua
local M = {}

local levels = {
    DEBUG = 1,
    INFO = 2,
    WARN = 3,
    ERROR = 4
}

local currentLevel = levels.INFO

function M.setLevel(level)
    currentLevel = levels[level] or levels.INFO
end

local function log(level, message)
    if levels[level] >= currentLevel then
        print(string.format("[%s] %s: %s", 
            os.date("%Y-%m-%d %H:%M:%S"),
            level,
            message))
    end
end

function M.debug(message)
    log("DEBUG", message)
end

function M.info(message)
    log("INFO", message)
end

function M.warn(message)
    log("WARN", message)
end

function M.error(message)
    log("ERROR", message)
end

return M
Output
Click Run to execute your code

Module Best Practices

Best Practices:
  • Always use local for the module table
  • Return the module table at the end
  • Keep private functions local (don't add to module table)
  • Use meaningful module names
  • Document your module's public API
  • Avoid global variables in modules
  • Keep modules focused on a single responsibility

Module Template

-- module_name.lua
-- Description: What this module does
-- Author: Your name
-- Version: 1.0

local M = {}

-- Private variables
local privateVar = "private"

-- Private functions
local function privateFunction()
    -- Implementation
end

-- Public functions
function M.publicFunction()
    -- Implementation
    privateFunction()  -- Can call private functions
end

-- Constants
M.VERSION = "1.0"
M.CONSTANT = "value"

return M

Practice Exercise

Try these module challenges:

Output
Click Run to execute your code

Summary

In this lesson, you learned:

  • What modules are and why they're important
  • Using require() to load modules
  • Different module patterns (basic, private functions, state)
  • Creating class modules
  • Organizing modules in directories
  • Practical examples: string utils, config, logger
  • Module best practices and templates

What's Next?

Congratulations on completing Module 5! You've mastered OOP and modules in Lua. Next, we'll explore advanced topics including error handling, debugging, and best practices for writing robust Lua code. Let's continue! 🚀