Skip to content

LTDev-LLC/nginx-sqlite-module

Repository files navigation

ngx_http_sqlite_module

CI

ngx_http_sqlite_module is a standalone Nginx dynamic module that turns a location into a configured SQLite-backed HTTP operation. SQL stays in Nginx config, while clients can only provide values for declared bound parameters.

This folder is self-contained so it can be built with:

./configure ... --with-compat --add-dynamic-module=/path/to/nginx_sql_module
make modules

Quick Start On Linux

cd nginx-sqlite-module
sudo make install

The installer supports hosts with apk, apt-get, dnf, or yum. It builds against the installed Nginx version and installs:

/usr/lib/nginx/modules/ngx_http_sqlite_module.so
/etc/nginx/modules/50-ngx-http-sqlite-module.conf
/usr/local/share/ngx_http_sqlite_module/

Then include module-backed locations in an Nginx server block:

sqlite_db "/var/lib/ngx_http_sqlite_module/app.sqlite";
sqlite_bearer_token "change-me";
sqlite_response json;
sqlite_busy_timeout 3000ms;
sqlite_max_response_size 8m;

location = /api/health-row {
    sqlite;

    sqlite_operation health;
    sqlite_method GET;
    sqlite_sql "SELECT 'ok' AS status";
    sqlite_read_only on;
    sqlite_allow_anon on;
}

Configuration Examples

Declare shared defaults once at server scope, then keep each location focused on one HTTP operation:

server {
    listen 8080;

    sqlite_db "/var/lib/app/app.sqlite";
    sqlite_bearer_token "change-me";
    sqlite_response json;
    sqlite_warmup on;

    location = /api/ready {
        sqlite;

        sqlite_operation ready;
        sqlite_method GET;
        sqlite_sql "SELECT 'ready' AS status";
        sqlite_read_only on;
        sqlite_allow_anon on;
    }
}

By default, workers warm up configured SQLite handles during startup and cap successful JSON responses at 8m. Use sqlite_warmup off; for lazy first-use setup, sqlite_busy_timeout 0; to fail immediately on lock contention, or sqlite_max_response_size off; for trusted routes that intentionally return larger JSON bodies.

Public JSON Read

location = /api/things/detail {
    sqlite;

    sqlite_operation get_thing;
    sqlite_method GET;
    sqlite_sql "SELECT id, name, json(attrs) AS attrs, updated_at AS updatedAt FROM things WHERE id = :id";
    sqlite_param id arg:id required raw;
    sqlite_read_only on;
    sqlite_allow_anon on;
    sqlite_json_value attrs;
    sqlite_empty_status 404;
}

Example request:

curl 'http://localhost:8080/api/things/detail?id=thing-1'

JSON Write With Bearer Auth

location = /api/things {
    sqlite;
    sqlite_max_body_size 64k;

    sqlite_operation upsert_thing;
    sqlite_method POST PUT PATCH;
    sqlite_sql "INSERT INTO things (id, name, attrs, updated_at) VALUES (:id, :name, json(:attrs), strftime('%s','now')) ON CONFLICT(id) DO UPDATE SET name = excluded.name, attrs = excluded.attrs, updated_at = excluded.updated_at RETURNING id, name, json(attrs) AS attrs, updated_at AS updatedAt";
    sqlite_param id json:id required raw;
    sqlite_param name json:name required raw;
    sqlite_param attrs json:attrs "default:{}" raw;
    sqlite_json_value attrs;
    sqlite_invalid_json_status 422;
}

Example request:

curl -X POST http://localhost:8080/api/things \
  -H 'Authorization: Bearer change-me' \
  -H 'Content-Type: application/json' \
  -d '{"id":"thing-1","name":"Sensor","attrs":{"room":"lab"}}'

Argon2id Credentials

Use the argon2id transform to store password or API-key hashes, then verify submitted secrets with sqlite_argon2id_verify:

location = /api/login {
    sqlite;

    sqlite_operation login;
    sqlite_method POST;
    sqlite_sql "SELECT username FROM users WHERE username = :username AND sqlite_argon2id_verify(password_hash, :password) = 1";
    sqlite_param username json:username required raw;
    sqlite_param password json:password required raw;
    sqlite_empty_status 401;
}

Because Argon2id uses a fresh salt, do not look up credentials by rehashing the submitted secret. For API keys, store a public key id or prefix separately and use it to select candidate rows before calling sqlite_argon2id_verify.

Multi-Statement Batch

Use sqlite_batch when a route needs multiple configured statements, such as a transaction that writes and then returns the changed row:

location = /api/things/batch {
    sqlite;

    sqlite_operation upsert_thing_batch;
    sqlite_method POST;
    sqlite_batch "
        BEGIN;
        INSERT INTO things (id, name, attrs, updated_at)
        VALUES (:id, :name, json(:attrs), strftime('%s','now'))
        ON CONFLICT(id) DO UPDATE SET
            name = excluded.name,
            attrs = excluded.attrs,
            updated_at = excluded.updated_at;
        SELECT id, name, json(attrs) AS attrs, updated_at AS updatedAt
        FROM things
        WHERE id = :id;
        COMMIT;
    ";
    sqlite_param id json:id required raw;
    sqlite_param name json:name required raw;
    sqlite_param attrs json:attrs "default:{}" raw;
    sqlite_json_value attrs;
}

If the configured SQL does not include explicit transaction statements, add sqlite_batch_transaction on; to have the module wrap the batch in BEGIN IMMEDIATE and COMMIT.

Status-Only Delete

location = /api/things {
    sqlite;

    sqlite_operation delete_thing;
    sqlite_method DELETE;
    sqlite_sql "DELETE FROM things WHERE id = :id";
    sqlite_param id arg:id required raw;
    sqlite_require_changes on;
    sqlite_response status_only;
    sqlite_success_status 204;
    sqlite_empty_status 404;
}

Docker Compose Test Harness

The subproject includes a Compose setup that builds the dynamic module and the third-party hook sample against the Nginx image, loads examples/example-nginx.conf, applies the bundled example migration, serves the API reference at http://localhost:8080/, serves the OpenAPI document at http://localhost:8080/openapi.json, and serves the example routes on port 8080:

cd nginx-sqlite-module
docker compose up --build

The Dockerfile defaults to nginx:alpine, bootstraps make, and runs make install KEEP_BUILD_DEPS=true NO_RELOAD=true inside the image so the example uses the same installer path as a host install before compiling the hook sample. Pass --build-arg NGINX_IMAGE=nginx:stable-alpine to test another compatible Nginx Alpine image, or --build-arg NGINX_PACKAGE_SOURCE=alpine to force Alpine's native Nginx package.

Smoke-test the running example server:

scripts/smoke-examples.sh

Benchmark one example response type:

scripts/bench-examples.sh --type json --requests 200
scripts/bench-examples.sh --type json_rows --requests 200
scripts/bench-examples.sh --type status --requests 200

Or run individual requests:

curl http://localhost:8080/examples/bootstrap
curl -X POST http://localhost:8080/examples/devices/json \
  -H 'Authorization: Bearer change-me' \
  -H 'Content-Type: application/json' \
  -d '{"id":"device-1","name":"Sensor","attrs":{"room":"lab"}}'
curl 'http://localhost:8080/examples/devices/detail?id=device-1'
curl 'http://localhost:8080/examples/devices/list?limit=10&offset=0'
curl -X POST http://localhost:8080/examples/projects/flags \
  -H 'Authorization: Bearer change-me' \
  -H 'Content-Type: application/json' \
  -d '{"projectId":"project-1","key":"checkout_v2","enabled":true,"rollout":100,"rules":{"plans":["pro"]}}'
curl 'http://localhost:8080/examples/projects/flags/list?projectId=project-1'
curl 'http://localhost:8080/examples/projects/flags/evaluate?projectId=project-1&key=checkout_v2&subject=user-1'

The example SQLite files are stored in the ngx_sqlite_module_data Docker volume. Remove the test data with:

docker compose down -v

Documentation

Security Model

Clients cannot submit SQL. Each route executes SQL configured with sqlite_sql or sqlite_batch, and user input is bound through explicit sqlite_param declarations. Use sqlite_bearer_token for admin routes, sqlite_allow_anon on; for intentional public read-only routes, avoid logging secrets in URLs by using JSON bodies, and use Argon2id for stored API keys, access tokens, and user passwords.

About

Nginx dynamic module for building SQLite-backed HTTP endpoints with configured SQL, JSON responses, auth controls, read-only routes, migrations, Docker examples, and multi-statement batches.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors