A high-performance CRUD API backend built entirely in Lua embedded in Nginx via OpenResty, eliminating traditional application servers. Demonstrates edge computing where "the boundary between 'web server' and 'application server' is artificial."
- Zero Application Server - Pure Nginx + Lua, no Node.js/Python/Go runtime
- PostgreSQL with Connection Pooling - Using
pgmoonfor async database access - JWT Authentication - Stateless auth with token blacklisting for logout
- Three-Strategy Rate Limiting - Global IP, per-user, per-endpoint using
lua-resty-limit-traffic - Input Validation & Security - SQL injection, XSS, path traversal, command injection detection
- Response Caching with ETags - LRU cache with conditional requests (304 Not Modified)
- Structured JSON Logging - Request/response logging with sanitization
- Docker Compose Deployment - Production-ready with monitoring stack
- Test::Nginx Integration Tests - Comprehensive API test suite
┌─────────────────────────────────────────────────────────────────┐
│ NGINX MASTER │
└──────────────────────────┬──────────────────────────────────────┘
│
┌──────────────────┼──────────────────┐
▼ ▼ ▼
┌───────────────┐ ┌───────────────┐ ┌───────────────┐
│ WORKER 1 │ │ WORKER 2 │ │ WORKER N │
│ ┌─────────┐ │ │ ┌─────────┐ │ │ ┌─────────┐ │
│ │ Lua VM │ │ │ │ Lua VM │ │ │ │ Lua VM │ │
│ │ │ │ │ │ │ │ │ │ │ │
│ │Router │ │ │ │Router │ │ │ │Router │ │
│ │Middleware│ │ │ │Middleware│ │ │ │Middleware│ │
│ │Handlers │ │ │ │Handlers │ │ │ │Handlers │ │
│ │DB Pool │ │ │ │DB Pool │ │ │ │DB Pool │ │
│ │Cache │ │ │ │Cache │ │ │ │Cache │ │
│ └─────────┘ │ │ └─────────┘ │ │ └─────────┘ │
└───────────────┘ └───────────────┘ └───────────────┘
│ │ │
└──────────────────┼──────────────────┘
▼
┌─────────────────────────┐
│ POSTGRESQL │
│ (Connection Pool) │
└─────────────────────────┘
- Docker & Docker Compose
- Git
# Clone the repository
git clone https://github.com/YOUR_USERNAME/lua-nginx-edge-backend.git
cd lua-nginx-edge-backend
# Start all services
docker compose -f docker/docker-compose.yml up -d
# View logs
docker compose -f docker/docker-compose.yml logs -f openresty
# Run tests
docker compose -f docker/docker-compose.yml exec openresty prove -I /usr/local/openresty/site/lib/perl5 tests/integration.t# Set required environment variables
export JWT_SECRET="your-super-secret-jwt-key-min-32-chars"
# Deploy
docker compose -f docker/docker-compose.yml up -d --build
# Scale workers
docker compose -f docker/docker-compose.yml up -d --scale openresty=3| Method | Endpoint | Description |
|---|---|---|
| POST | /api/v1/auth/register |
Register new user |
| POST | /api/v1/auth/login |
Login and get JWT token |
| POST | /api/v1/auth/refresh |
Refresh JWT token |
| POST | /api/v1/auth/logout |
Logout (blacklist token) |
| GET | /api/v1/auth/me |
Get current user info |
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/v1/items |
List items (paginated, filterable) |
| POST | /api/v1/items |
Create new item |
| GET | /api/v1/items/:id |
Get single item |
| PUT | /api/v1/items/:id |
Full update item |
| PATCH | /api/v1/items/:id |
Partial update item |
| DELETE | /api/v1/items/:id |
Delete item |
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/v1/users/me |
Get current user profile |
| PUT | /api/v1/users/me |
Update profile |
| PUT | /api/v1/users/me/password |
Change password |
| GET | /api/v1/users |
List users (admin) |
| GET | /api/v1/users/:id |
Get user by ID (admin) |
| DELETE | /api/v1/users/:id |
Delete user (admin) |
| Method | Endpoint | Description |
|---|---|---|
| GET | /health |
Health check |
| GET | /metrics |
Prometheus metrics |
# Register
curl -X POST http://localhost:8080/api/v1/auth/register \
-H "Content-Type: application/json" \
-d '{"username": "johndoe", "email": "john@example.com", "password": "SecurePass123!"}'
# Login
curl -X POST http://localhost:8080/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{"email": "john@example.com", "password": "SecurePass123!"}'
# Response: {"success":true,"data":{"user":{...},"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."}}# Set token
TOKEN="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
# Create item
curl -X POST http://localhost:8080/api/v1/items \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"title": "My Item", "description": "Description", "metadata": {"tags": ["tag1", "tag2"]}}'
# List items with pagination
curl -X GET "http://localhost:8080/api/v1/items?page=1&per_page=10" \
-H "Authorization: Bearer $TOKEN"
# Get single item
curl -X GET http://localhost:8080/api/v1/items/1 \
-H "Authorization: Bearer $TOKEN"
# Update item
curl -X PUT http://localhost:8080/api/v1/items/1 \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"title": "Updated Title"}'
# Delete item
curl -X DELETE http://localhost:8080/api/v1/items/1 \
-H "Authorization: Bearer $TOKEN"All responses include rate limit headers:
X-RateLimit-Limit-Global: 100
X-RateLimit-Remaining-Global: 99
X-RateLimit-Reset-Global: 1699999999
X-RateLimit-Limit-User: 1000
X-RateLimit-Remaining-User: 999
X-RateLimit-Reset-User: 1699999999
X-RateLimit-Limit-Endpoint: 50
X-RateLimit-Remaining-Endpoint: 49
X-RateLimit-Reset-Endpoint: 1699999999
# First request - returns ETag
curl -I http://localhost:8080/api/v1/items -H "Authorization: Bearer $TOKEN"
# ETag: "a1b2c3d4e5f6..."
# Conditional request - returns 304 if unchanged
curl -I http://localhost:8080/api/v1/items \
-H "Authorization: Bearer $TOKEN" \
-H 'If-None-Match: "a1b2c3d4e5f6..."'| Variable | Default | Description |
|---|---|---|
DB_HOST |
postgres |
PostgreSQL host |
DB_PORT |
5432 |
PostgreSQL port |
DB_NAME |
edge_backend |
Database name |
DB_USER |
edge_user |
Database user |
DB_PASSWORD |
edge_pass |
Database password |
DB_POOL_SIZE |
10 |
Connection pool size per worker |
DB_TIMEOUT |
5000 |
Query timeout (ms) |
JWT_SECRET |
required | JWT signing secret (min 32 chars) |
JWT_EXPIRES_IN |
3600 |
Token expiry (seconds) |
NGINX_WORKER_PROCESSES |
auto |
Nginx worker processes |
NGINX_WORKER_CONNECTIONS |
1024 |
Connections per worker |
Configure in nginx/nginx.conf (init_by_lua_block):
_G.rate_limit_config = {
global = { window = 60, limit = 100 }, -- 100 req/min per IP
user = { window = 60, limit = 1000 }, -- 1000 req/min per user
endpoint = { window = 60, limit = 50 }, -- 50 req/min per endpoint per IP
}lua-nginx-edge-backend/
├── docker/
│ ├── Dockerfile.openresty # Multi-stage OpenResty build
│ ├── docker-compose.yml # Full stack deployment
│ └── init-db/
│ └── 01-schema.sql # Database schema
├── lua/
│ ├── handlers/ # Route handlers
│ │ ├── auth.lua # Authentication endpoints
│ │ ├── items.lua # Items CRUD
│ │ └── users.lua # User management
│ ├── middleware/ # Request/response middleware
│ │ ├── jwt_auth.lua # JWT validation
│ │ ├── rate_limit.lua # Three-strategy rate limiting
│ │ ├── validate.lua # Input validation
│ │ └── logging.lua # Structured logging
│ ├── utils/ # Shared utilities
│ │ ├── response.lua # API response formatting
│ │ ├── database.lua # PostgreSQL connection pool
│ │ ├── validate.lua # Validation helpers
│ │ └── jwt.lua # JWT token management
│ └── router.lua # Main request router
├── nginx/
│ └── nginx.conf # OpenResty configuration
├── tests/
│ ├── integration.t # Test::Nginx integration tests
│ └── unit/
│ └── test_validate.lua # Busted unit tests
├── docs/
│ └── api.md # Detailed API documentation
├── scripts/
│ └── deploy.sh # Deployment helper
├── .gitignore
├── LICENSE
└── README.md
The Docker Compose includes:
- Prometheus (port 9090) - Metrics collection
- Grafana (port 3000) - Dashboards (admin/admin)
- Loki (port 3100) - Log aggregation
- Promtail - Log shipping to Loki
Access Grafana at http://localhost:3000 with default credentials admin/admin.
# Run all integration tests
docker compose -f docker/docker-compose.yml exec openresty prove tests/integration.t
# Run with verbose output
docker compose -f docker/docker-compose.yml exec openresty prove -v tests/integration.t# Install busted
luarocks install busted
# Run unit tests
busted tests/unit/# Install hey
go install github.com/rakyll/hey@latest
# Load test
hey -n 10000 -c 100 -H "Authorization: Bearer $TOKEN" http://localhost:8080/api/v1/items- Change Default Secrets - Set
JWT_SECRETto a strong random value (32+ chars) - Use HTTPS - Terminate TLS at load balancer or configure Nginx SSL
- Database Passwords - Use Docker secrets or external secret management
- Rate Limiting - Adjust limits based on your traffic patterns
- Input Validation - All inputs sanitized; SQL injection/XSS patterns blocked
- Security Headers - CSP, X-Frame-Options, HSTS included
- Token Blacklisting - Logout invalidates tokens immediately
worker_processes auto; # Uses all CPU cores
worker_connections 1024; # Per workerOpenResty uses LuaJIT 2.1 for maximum performance. Ensure:
lua_code_cache onin production (default)- Pre-load modules in
init_by_lua_block
_G.db_config.pool_size = 10 -- Per worker
-- Total connections = workers * pool_size-- Response cache (per worker)
local cache, err = lrucache.new(10000) -- 10k entries- Fork the repository
- Create feature branch (
git checkout -b feature/amazing-feature) - Commit changes (
git commit -m 'Add amazing feature') - Push to branch (
git push origin feature/amazing-feature) - Open Pull Request
MIT License - see LICENSE for details.
- OpenResty - The foundation
- pgmoon - PostgreSQL driver
- lua-resty-jwt - JWT handling
- lua-resty-limit-traffic - Rate limiting
- Test::Nginx - Integration testing
Built with ❤️ using OpenResty and Lua