Create simple, lightweight and customizable HTTP servers
Warning
Experimental project. APIs may change without notice.
- About
- Dependencies
- Getting Started
- CORS
- Pipeline
- Cache
- Complete Example
- Examples
- Roadmap
- License
- Contributing
Pudim Server is a Lua library focused on providing simple APIs for creating web servers with a routing system. It's designed to accept HTTP requests, parse them, dispatch to a handler, and return a response.
This library is being designed based on what Davi understands about servers. He intends to expand the library into something more complex over time, while keeping the focus on simplicity, lightness, and customization.
- Lua >= 5.4
- LuaSocket
- lua-cjson
- loglua
# Local installation
luarocks install PudimServer --local
# Global installation
sudo luarocks install PudimServerlocal PudimServer = require("PudimServer")
local server = PudimServer:Create{
ServiceName = "My Service",
Port = 8080,
Address = "localhost"
}All fields are optional. Defaults: ServiceName = "Pudim Server", Port = 8080, Address = "localhost".
Route handlers receive (req, res) and must return res:response(status, body, headers).
-- HTML page
server:Routes("/", function(req, res)
if req.method == "GET" then
return res:response(200, "<h1>Hello!</h1>", {["Content-Type"] = "text/html"})
end
return res:response(405, {error = "Method not allowed"})
end)
-- JSON API (tables are auto-encoded to JSON)
server:Routes("/api/users", function(req, res)
if req.method == "GET" then
return res:response(200, {
{id = 1, name = "John"},
{id = 2, name = "Mary"}
})
end
if req.method == "POST" then
return res:response(201, {msg = "User created!", data = req.body})
end
return res:response(405, {error = "Method not allowed"})
end)server:Run()| Property | Type | Description |
|---|---|---|
method |
string | HTTP method (GET, POST, PUT, DELETE, etc) |
path |
string | Request path (e.g.: "/api/users") |
version |
string | HTTP version (e.g.: "HTTP/1.1") |
headers |
table | Request headers (keys in lowercase) |
body |
string | Request body |
query |
table<string, string|string[]>? | Parsed query string |
params |
table<string, string>? | Dynamic route params |
res:response(status, body, headers):
| Parameter | Type | Required | Description |
|---|---|---|---|
status |
number | ✅ | HTTP status code (200, 404, 500, etc) |
body |
string/table | ✅ | Response body. Tables are auto-converted to JSON |
headers |
table | ❌ | Custom response headers |
Enable CORS with EnableCors(). Preflight OPTIONS requests are handled automatically.
-- Allow all origins (defaults)
server:EnableCors()
-- Restricted configuration
server:EnableCors{
AllowOrigins = {"https://myapp.com", "https://admin.myapp.com"},
AllowMethods = {"GET", "POST"},
AllowHeaders = "Content-Type, Authorization",
AllowCredentials = true,
ExposeHeaders = {"X-Total-Count"},
MaxAge = 3600
}| Option | Type | Default |
|---|---|---|
AllowOrigins |
string | string[] | "*" |
AllowMethods |
string | string[] | "GET, POST, PUT, DELETE, PATCH, OPTIONS" |
AllowHeaders |
string | string[] | "Content-Type, Authorization" |
ExposeHeaders |
string | string[] | "" |
AllowCredentials |
boolean | false |
MaxAge |
number | 86400 |
The pipeline system lets you add request/response handlers that run before your route handlers. Each handler receives (req, res, next) — call next() to continue or return early to short-circuit.
-- Request logger
server:UseHandler{
name = "logger",
Handler = function(req, res, next)
print(req.method .. " " .. req.path)
return next()
end
}
-- Authentication (short-circuits on failure)
server:UseHandler{
name = "auth",
Handler = function(req, res, next)
if req.path:find("^/api/protected") and not req.headers["authorization"] then
return res:response(401, {error = "Unauthorized"})
end
return next()
end
}
-- Response wrapper
server:UseHandler{
name = "timer",
Handler = function(req, res, next)
local start = os.clock()
local response = next()
print(("Request took %.3fs"):format(os.clock() - start))
return response
end
}
-- Remove a handler
server:RemoveHandler("logger")Handlers run in the order they are registered. The route handler runs last.
In-memory response cache with TTL and automatic eviction. Only GET requests are cached.
local Cache = require("PudimServer.cache")
-- Create cache instance
local cache = Cache.new{
MaxSize = 200, -- max entries (default: 100)
DefaultTTL = 120 -- seconds (default: 60)
}
-- Add to pipeline (easiest way)
server:UseHandler(Cache.createPipelineHandler(cache))
-- Or use manually in a route
server:Routes("/api/data", function(req, res)
local cached = cache:get("my-key")
if cached then return cached end
local response = res:response(200, {data = "expensive computation"})
cache:set("my-key", response, 30) -- custom TTL
return response
end)
-- Invalidate entries
cache:invalidate("my-key")
cache:clear()| Option | Type | Default | Description |
|---|---|---|---|
MaxSize |
number | 100 |
Max entries before eviction |
DefaultTTL |
number | 60 |
TTL in seconds |
local PudimServer = require("PudimServer")
local Cache = require("PudimServer.cache")
local server = PudimServer:Create{
ServiceName = "My API",
Port = 3000,
Address = "localhost"
}
-- Enable CORS
server:EnableCors()
-- Add request logger
server:UseHandler{
name = "logger",
Handler = function(req, res, next)
print(("[%s] %s %s"):format(os.date("%H:%M:%S"), req.method, req.path))
return next()
end
}
-- Add cache (GET only, 30s TTL)
local cache = Cache.new{ DefaultTTL = 30 }
server:UseHandler(Cache.createPipelineHandler(cache))
-- Routes
server:Routes("/", function(req, res)
if req.method == "GET" then
return res:response(200, "<h1>Welcome!</h1>", {["Content-Type"] = "text/html"})
end
return res:response(405, {error = "Method not allowed"})
end)
server:Routes("/api/data", function(req, res)
if req.method == "GET" then
return res:response(200, {
status = "ok",
timestamp = os.time(),
message = "API working!"
})
end
return res:response(405, {error = "Method not allowed"})
end)
print("Server running at http://localhost:3000")
server:Run()Functional examples are available in the examples/ directory:
| File | Description |
|---|---|
json_response_example.lua |
Auto JSON encoding for table responses |
cors_example.lua |
CORS with default settings |
cors_restricted_example.lua |
CORS with restricted origins and credentials |
pipeline_example.lua |
Logger, auth and custom header pipeline handlers |
cache_example.lua |
Pipeline cache and manual cache usage |
query_dynamic_example.lua |
Query string parsing and dynamic route params |
https_concurrency_example.lua |
Native HTTPS (luasec) with cooperative concurrency |
hot_reload_example.lua |
Development hot reload without file watcher |
complete_featured_example.lua |
Full setup with CORS, pipeline, cache, query, dynamic routes, hot reload, and optional HTTPS |
Run any example with:
lua ./examples/json_response_example.luaNote
This project is in experimental phase. Check below what has been implemented and what is planned.
- Basic routing system
- HTTP request parsing (method, path, headers, body, query, params)
- Automatic JSON responses (tables converted to JSON)
- Configurable logging system
- CORS helpers (Cross-Origin Resource Sharing)
- Request/response pipeline (middleware at HTTP level)
- In-memory response cache with TTL
- Dynamic routes with parameters (
/users/:id,/posts/:slug) - Automatic query string parsing (
?page=1&limit=10) - More HTTP status codes mapped
- Multi-threading / concurrency (cooperative coroutines)
- Native HTTPS support (luasec)
- Hot reload in development (without file watcher)
- File upload support (multipart/form-data)
- Serve static files (HTML, CSS, JS, images)
- Basic rate limiting
- WebSocket support
Enable hot reload by invalidating selected Lua modules from package.loaded on each request.
local server = PudimServer:Create{
Port = 8080,
HotReload = {
Enabled = true,
Modules = {"examples.hot_reload_message"},
Prefixes = {"app."}
}
}Use Modules for exact module names and Prefixes to invalidate groups. This is for development only.
This project is under the MIT license. See the LICENSE file for more details.
Contributions are welcome! See the complete guide at CONTRIBUTING.md.
Made with ❤️ by Davi