Building APIs with Lua
Introduction
Welcome to the third lesson of our "Extending NGINX with Lua and OpenResty" course! You've come a long way: we started by embedding Lua code in NGINX to generate dynamic content, then learned to manipulate HTTP traffic by reading and modifying headers and parameters. Now, we're ready for something more ambitious: building a complete REST API using only Lua code within OpenResty. We'll implement all the essential operations — creating, reading, updating, and deleting resources — while handling different HTTP methods, parsing JSON data, and managing application state.
Understanding REST APIs
Before we dive into code, let's establish what we're building. A REST API (Representational State Transfer) is an architectural pattern for creating web services that treat everything as a resource. Each resource has a unique URL, and we use standard HTTP methods to perform operations on it:
- GET: Retrieve resource(s)
- POST: Create a new resource
- PUT: Update an existing resource
- DELETE: Remove a resource
We'll build an API for managing items, where each item has properties like name, description, and created_at/updated_at timestamps. Our API will accept and return JSON, the standard data format for modern web services. The beauty of doing this in OpenResty is that everything happens within NGINX itself — no separate application server needed.
Setting Up In-Memory Storage
To store our items correctly in OpenResty, we should avoid using plain Lua global variables for shared state. In NGINX/OpenResty, each worker process has its own Lua VM, so global variables are per-worker and are not reliably shared across the server.
Instead, we'll use an OpenResty shared memory dictionary for in-memory storage that is visible across all workers:
Here's how we'll use it:
ngx.shared.items: Accesses the shared dictionary at request time- We store each item under a key like
item:1,item:2, etc. as a JSON string - We generate unique IDs with an atomic counter:
items:incr("items_counter", 1, 0)
This keeps our example in-memory (no database) while matching NGINX’s multi-worker architecture. In production, you'd typically use a database, but this approach keeps our example focused on the API mechanics.
