Declarative, database‑driven REST endpoints generated from JSON configuration.
Ship full CRUD plus advanced data operations (GET / POST / PUT / DELETE / PATCH / TRACE, import & export) for any table — without writing boilerplate code. Built with Rust + Actix Web.
flx-nocode-api · supports MySQL · PostgreSQL · SQLite · MSSQL · MongoDB
- What is Flexurio?
- How it works
- Quick start (SQLite)
- Installation
- Environment variables (
.env) - Configuration layout (
LOC_CONFIG) - Entity schema reference
- Custom ID generation (
function) - Hooks & validation
- Formula placeholders
- Endpoint reference
- Authentication & authorization
- Import & export
- Column encryption
- Logging & observability
- Database feature flags (compile‑time)
- Multi‑target build script (
build.sh) - Troubleshooting
- Security checklist
- Contributing & license
Flexurio No‑Code API lets you stand up secure, multi‑database REST endpoints by describing each entity (table) in a JSON file. You write configuration, not code; the engine generates the HTTP surface, validates requests, talks to the database, and enforces authentication.
Typical use cases:
- Rapid prototyping of admin / data panels.
- Internal tooling and back‑office APIs.
- Putting a clean REST API over an existing MySQL / Postgres / SQLite / MSSQL / MongoDB database.
- Computed / formula‑driven flows and change‑capture journaling (PATCH & TRACE).
At startup the engine:
- Reads the active config profile path from the
LOC_CONFIGenvironment variable. - Loads enabled route names from
LOC_CONFIG/routes.json. - Loads one entity schema per route from
LOC_CONFIG/entity/<route>.json. - Ensures the core tables (
flx_users,flx_roles) exist and seeds a default admin if none exists. - Registers a uniform REST surface for every route (only the HTTP methods you enable per schema).
- Applies JWT authentication to all non‑public routes (with public overrides and an optional IP allow‑list).
┌────────────────────┐
routes.json │ enabled routes │
└─────────┬──────────┘
│ for each route
▼
LOC_CONFIG/entity/<route>.json ──► TableSchema
│
▼
┌──────────────────────────────────────────────────────────┐
│ Actix‑web router │
│ GET /<route> POST /<route> PUT /<route>/{id} │
│ DELETE /<route>/{id} PATCH /<route> TRACE /<route> │
│ POST /import/<route> GET /export/<route> │
│ GET /validate/<route> POST /generate/table/<route> │
└──────────────────────────────────────────────────────────┘
│
▼
DB adapter (MySQL · Postgres · SQLite · MSSQL · MongoDB)
The fastest way to try Flexurio — no external database needed.
# 1. Get a binary (build from source shown here; see §4 for installers)
cargo build --release
# 2. Create a minimal .env
cat > .env <<'EOF'
DB_TYPE=sqlite
SQLITE_URL=sqlite://data.db
LOC_CONFIG=config
SECRET_KEY=replace_with_a_long_random_secret
ENCRYPT_KEY=replace_with_another_random_secret
PORT=8080
REQUIRE_AUTH=True
DEBUG=True
LOGGING=True
EOF
# 3. Run it
./target/release/flx-nocode-apiOn first start the engine creates flx_users / flx_roles if missing and, if there is no admin yet, prints a generated password to the console:
Your admin Password: 1234
Log in with email admin and that password (see §12).
- macOS or Linux (Windows works natively or via WSL).
- A database. SQLite needs nothing extra and is ideal for a first run.
- Rust toolchain only if you build from source.
curl -fsSL https://raw.githubusercontent.com/flexurio/flx-nocode-api/main/install-flexurio.sh | bashThe script detects your OS/architecture, downloads the matching asset from the latest GitHub release, installs the binary plus a convenient flexurio wrapper into ~/.local/bin, and adds it to your PATH. The flexurio command automatically reads the .env in the current working directory.
Reload your shell afterwards (e.g. source ~/.zshrc), then run flexurio from any folder containing a .env.
Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass
.\install-flexurio.ps1
# open a new terminal, then:
flexurio# macOS (Apple Silicon)
curl -fsSL -o flx-nocode-aarch64-apple-darwin.pkg \
https://github.com/flexurio/flx-nocode-api/releases/latest/download/flx-nocode-aarch64-apple-darwin.pkg
sudo installer -pkg flx-nocode-aarch64-apple-darwin.pkg -target /
# Linux (x86_64)
curl -fsSL -o flx-nocode-x86_64-unknown-linux-gnu \
https://github.com/flexurio/flx-nocode-api/releases/latest/download/flx-nocode-x86_64-unknown-linux-gnu
chmod +x flx-nocode-x86_64-unknown-linux-gnu
install -m 0755 flx-nocode-x86_64-unknown-linux-gnu "$HOME/.local/bin/flx-nocode"git clone https://github.com/flexurio/flx-nocode-api.git
cd flx-nocode-api
cargo build --release
./target/release/flx-nocode-apiTo build a smaller binary with only the database backend(s) you need, see §16.
docker-compose.yaml is included as a reference. Mount your static/, config/, and .env:
services:
rust-app:
build: .
container_name: flx-nocode-api
restart: always
ports:
- "2121:8080" # access at http://localhost:2121
volumes:
- "./static:/app/static"
- "./config:/app/config"
- "./.env:/app/.env"Copy the bundled example and edit it:
cp env .envPick exactly one
DB_TYPEand its matching URL. Avoid duplicate keys.
| Variable | Required | Description |
|---|---|---|
PORT |
Yes | HTTP listen port (e.g. 8080). |
DB_TYPE |
Yes | One of mysql, postgres, sqlite, mssql, mongodb. |
MYSQL_URL / POSTGRES_URL / SQLITE_URL / MSSQL_URL / MONGODB_URI |
Cond. | Connection string for the selected backend. SQLite quick start: sqlite://data.db. |
MONGODB_DB |
Cond. | Database name when DB_TYPE=mongodb. |
SECRET_KEY |
Yes | HMAC secret used to sign Flexurio‑issued JWTs. |
ENCRYPT_KEY |
Yes | Symmetric key for encrypted columns (encrypt: true). |
LOC_CONFIG |
Yes | Path to the active config profile (contains routes.json + entity/). |
REQUIRE_AUTH |
No | True (default) to enforce JWT auth; False exposes routes without auth. |
BASE_URL |
No | External base URL used in logs / links. |
| Variable | Default | Description |
|---|---|---|
LOC_STATIC |
static |
Directory served under /static. |
LOC_IMAGE |
images |
Image upload directory (inside the static directory). |
LOC_LOGGING |
logs |
Directory for log files. |
LOC_AUDIT |
— | Path to the audit event log (keep outside static/ to avoid exposing it). |
| Variable | Description |
|---|---|
CUSTOME_JWT_QUERY |
SQL run at login to enrich the JWT cs claim. Use {:?} as the user‑id placeholder, e.g. SELECT email FROM flx_users WHERE id = {:?}. (CUSTOM_JWT_QUERY is also accepted.) |
WHITE_LIST_IP |
Comma‑separated IPs / CIDR ranges that bypass JWT validation. |
CONVERTER_JWT_SECRET |
HMAC secret to verify externally‑issued JWTs (converter‑token mode). |
CONVERTER_JWT_PUBLIC_KEY |
PEM public key (RS*/ES*/EdDSA) to verify external JWTs. Use literal \n for newlines. |
CONVERTER_JWT_ALG |
Algorithm for external JWT verification (e.g. HS256, RS256). |
CONVERTER_JWT_ISSUER / CONVERTER_JWT_AUDIENCE |
Optional issuer / audience claim checks (comma‑separated). |
CONVERTER_JWT_INSECURE_SKIP_VERIFY |
true to accept external JWTs without signature verification (only if an upstream gateway already validates them). |
Converter‑token mode is fail‑closed: if it is active and none of the verification variables are set, all converter‑token requests are rejected. See §12.
| Variable | Default | Description |
|---|---|---|
LIMIT_DEFAULT |
100 |
Default page size for GET. |
LIMIT_MAX |
1000 |
Maximum page size a client may request. |
JSON_LIMIT_KB |
512 |
Max JSON request body size. |
UPLOAD_LIMIT_MB |
5 |
Max upload size per file. |
UPLOAD_TEXT_LIMIT_KB |
512 |
Max size of a text form field. |
UPLOAD_MAX_FILES / UPLOAD_MAX_FIELDS |
5 / 100 |
Multipart limits. |
UPLOAD_EXT_ALLOW |
— | Comma‑separated allow‑list of upload file extensions. |
IMPORT_BATCH_SIZE |
— | Rows per batch during import. |
RATE_LIMIT_LOGIN_PER_MIN |
3 |
Login attempts per minute. |
RATE_LIMIT_MUTATE_PER_SEC |
20 |
Per‑second limit for mutating methods. |
RATE_LIMIT_GET_PER_SEC |
50 |
Per‑second limit for GET. |
RATE_LIMIT_LOGIN_FAIL_USER / RATE_LIMIT_LOGIN_FAIL_IP |
— | Failed‑login limits over a 5‑minute window. |
| Variable | Description |
|---|---|
ACTIX_WORKERS |
Number of worker threads (match CPU cores). |
HTTP_KEEPALIVE_SECS / HTTP_BACKLOG / HTTP_MAX_CONN_RATE / HTTP_MAX_CONNECTIONS |
HTTP server tuning. |
MAX_POOL / MIN_POOL / CONNECT_TIMEOUT / POOL_MAX_LIFETIME_SECS / POOL_IDLE_TIMEOUT_SECS |
DB connection‑pool tuning. |
WRITE_QUEUE_ENABLED / WRITE_CONCURRENCY / WRITE_QUEUE_MAX_LEN / WRITE_EXEC_RETRY_MAX |
Write‑queue / concurrency controls for high write throughput. |
DEFAULT_COLLATE |
Default collation for MySQL/MariaDB (e.g. utf8mb4_bin). |
MSSQL_ENCRYPTION / MSSQL_TRUST_CERT |
TLS options for MSSQL. |
REDIS_HOST / REDIS_PORT / REDIS_PASSWORD / REDIS_DB |
Redis connection (caching / extension). |
| Variable | Description |
|---|---|
DEBUG |
1/true/yes enables verbose debug logging. |
LOGGING |
True enables extended log output to LOC_LOGGING. |
LOG_MIN_LEVEL |
error|warn|info|debug|trace (default info). Drops messages below the level. |
LOG_MAX_BODY_BYTES |
Max bytes printed per log body (default 8192); longer bodies are truncated. |
LOG_SAMPLE_DEBUG_N |
Sample every N debug/trace logs (default 1 = no sampling). |
LOG_QUEUE_CAP |
Bounded logger queue capacity (default 2048). |
LOG_COLOR |
0/false/no disables ANSI colors. |
Logging runs on a non‑blocking background thread, so it never blocks the request path. In production prefer LOG_MIN_LEVEL=info (most SQL/param logs are at debug) and consider sampling under high load.
LOC_CONFIG/
routes.json # enabled routes + public routes
rules.json # (optional) role / endpoint authorization rules
entity/
<route>.json # one schema per route — file name must match the route
Several sample profiles are included: config, config/example, config/pos, config/tms, configmftl.
{
"routes": ["flx_users", "flx_roles", "banks", "bank_types"],
"route_publics": ["login", "register"]
}routes— entities to expose. Each must have a matchingentity/<name>.json.route_publics— routes reachable without a JWT.
- Add the name to
routesinroutes.json. - Create
entity/<route>.json(copy an existing one; ensuretableand the file name align). - (Optional)
POST /generate/table/<route>to create the physical table (requiresauto_generate: true). GET /validate/<route>to confirm the schema matches the database.- Use the CRUD endpoints.
Each entity/<route>.json deserializes into a TableSchema (see src/model.rs). Sections:
| Key | Purpose |
|---|---|
table |
Physical table / collection name. |
primary_key.columns |
Array of PK columns (supports composite keys). |
columns[] |
Column definitions (see below). |
foreign_keys[] |
{ column, reference_table, reference_column, on_delete, on_update }. Actions: cascade, restrict, set null, no action. |
indexes[] |
{ name, columns[], unique }. Unique indexes are enforced on insert/update. |
redis |
{ keys[], ttl } — cache blueprint. |
get |
Read pipeline (see below). |
post / put |
Create / update behavior + hooks (see §9). Suffix a name in columns with * to make that field required — e.g. "columns": ["name*", "phone"]. |
del |
{ enable_method, columns, type_delete, pre_process, post_process }; type_delete = soft or hard. |
patch |
Stored‑procedure / parameterized op: { enable_method, pre_process_sp, parameters[], return_mode }. return_mode = "" / rows / affected. |
trace |
Advanced insert + select / upsert pipeline for journaling & change capture. |
auto_generate |
If true, the POST /generate/table/<route> endpoint is exposed. |
collate |
Per‑table collation override. |
Each HTTP method is only registered when its section sets "enable_method": true.
{
"name": "id",
"type_data": "varchar(15)",
"auto_increment": false,
"nullable": false,
"function": "{request.id_trans}/%Y/%m/000ID",
"function_endpoint": "",
"function_endpoint_path": "data",
"encrypt": false,
"default": null
}| Field | Description |
|---|---|
name |
Column name. |
type_data |
SQL type, e.g. varchar(255), bigint, timestamp. |
auto_increment |
Auto‑increment integer PK. |
nullable |
Whether NULL is allowed. |
function |
(optional) A pattern that builds the column value automatically on insert — e.g. "{request.id_trans}/%Y/%m/000ID" produces SO/2026/01/0001. Empty string = no generation (the client supplies the value). Full token list in §8. |
function_endpoint |
(optional) When function contains a numeric …ID token, fetch the running number from this HTTP endpoint instead of computing MAX(id)+1. Empty string = use the built‑in MAX(id)+1. Supports {request.field} in the URL. Detail in §8. |
function_endpoint_path |
(optional) Dotted JSON path to the number inside the function_endpoint response. Defaults to data, i.e. a response of { "data": 1 }. Ignored when function_endpoint is empty. |
encrypt |
If true, the value is stored encrypted with ENCRYPT_KEY — see §14. |
default |
Default value used by generate/table. |
The three function* fields work together to auto‑generate an id; they only apply to POST/insert:
function— the format. Split on/; tokens like%Y/%m/%d(date),{request.field}(request value), andNNNID(zero‑padded running number) are resolved, everything else is literal.function_endpoint— where the running number comes from. Leave empty → the engine usesMAX(id)+1for ids sharing the same prefix. Set it → the engineGETs that URL (with the built prefix appended as?prefix=…and the request'sAuthorizationheader forwarded) and uses the returned number. There is no fallback: if the call fails the insert is aborted.function_endpoint_path— how to read the number out of the endpoint's JSON response (defaultdata).
A column with no id generation simply sets
"function": ""and omits the two endpoint fields. See §8 for worked examples.
"get": {
"enable_method": true,
"columns": ["banks.id", "banks.name", "bank_types.name"],
"parameters": ["name.eq", "bank_type_id.eq"],
"join_tables": [
{ "table": "bank_types", "columns": ["name"], "logical": "banks.bank_type_id = bank_types.id", "type_join": "left" }
],
"column_groups": [],
"having": [],
"order_by": ["banks.id"],
"where_clause": []
}parametersdeclares which query‑string filters are accepted, in the formcolumn.operator(e.g.name.eq,created_at.gte). Clients then callGET /banks?name.eq=BCA.- Pagination is controlled by
LIMIT_DEFAULT/LIMIT_MAX.
Set function on a column (typically id) to build a formatted identifier on insert — e.g. SO/2026/01/0001. The pattern is split on / and each token is resolved:
| Token | Result |
|---|---|
%Y |
Current 4‑digit year |
%m |
Current 2‑digit month |
%d |
Current 2‑digit day |
{request.field} |
A value from the request body (supports dotted paths) |
000ID (any digits + ID) |
The running/sequence number, zero‑padded to the number of leading digits (000ID → width 3 → 001) |
| anything else | Used literally |
Example pattern and the value it produces:
{ "name": "id", "type_data": "varchar(15)", "function": "{request.id_trans}/%Y/%m/000ID" }request.id_trans = "SO" → SO/2026/01/0001
By default the …ID token is computed as MAX(id) + 1 for ids sharing the same prefix, inside the same transaction as the insert. You can instead fetch it from an external endpoint — useful when sequence numbers are owned by another service.
Add function_endpoint (and optionally function_endpoint_path) to the same column:
{
"name": "id",
"type_data": "varchar(15)",
"function": "{request.id_trans}/%Y/%m/000ID",
"function_endpoint": "http://localhost:8080/api/next-sequence",
"function_endpoint_path": "data"
}Behavior when function_endpoint is set:
- The URL is interpolated (
{request.field}placeholders are filled from the request body). - The already‑built prefix is appended as a query param, URL‑encoded — e.g.
?prefix=SO%2F2026%2F01— so the endpoint can scope the sequence per prefix. - A
GETis sent; the inbound request'sAuthorizationheader is forwarded. - The response must be JSON. The number is read from
function_endpoint_path(dotted path, defaultdata) and coerced to an integer. - The number is zero‑padded to the token width and spliced into the id.
Expected response shape (with the default path data):
{ "data": 1 }→ produces SO/2026/01/0001.
No fallback: if the endpoint times out, returns a non‑2xx status, or the field is missing/non‑numeric, the insert is aborted with an error. Leave
function_endpointempty to use the built‑inMAX(id)+1strategy.
Implementation: src/nocode/repositories/data_create_repo.rs (fetch_next_number_from_endpoint and query_next_number_from_max).
post, put, and del can run extra logic around the main database operation. All hook strings are prefixed to indicate their kind; an empty string or a value without a recognized prefix is ignored.
| Schema field | Runs | Prefixes |
|---|---|---|
post.validate_data |
Before INSERT (reject on failure) | SQL: or API: |
post.pre_process |
Before INSERT | SQL: |
post.post_process |
After a successful INSERT | SQL: |
put.validate_data |
Before UPDATE (reject on failure) | SQL: or API: |
put.pre_process |
Before UPDATE | SQL: |
put.post_process |
After a successful UPDATE | SQL: |
del.pre_process |
Before DELETE | SQL: |
del.post_process |
After DELETE | SQL: |
These are the actual field names the engine reads. (Older drafts of this README referred to
before/after; those keys are not used.)
"post": {
"enable_method": true,
"pre_process": "SQL:UPDATE counters SET val = val + 1 WHERE name = 'menus'",
"post_process": "SQL:INSERT INTO audit_logs(entity, action, user_id) VALUES('menus','CREATE',{request.created_by_id})",
"columns": ["name"]
}- Placeholders are bound as parameters automatically (SQL‑injection safe) — you do not write
?yourself. - Each hook is a single statement. For multi‑step transactional logic, use a stored procedure (
patch.pre_process_sp) or DB triggers. pre_process/post_processrun in the operation's transaction on SQL backends;validate_dataruns first and can reject the request.
Call an external endpoint and assert on its response before allowing the write. Format:
API:<METHOD>:<URL>|<operator>:<response_path>:<request_path>
Example — only allow a role that the /roles endpoint returns:
"validate_data": "API:GET:http://127.0.0.1:8080/roles|in:data:request.role"SQL: validation expects a query returning an is_valid boolean:
"validate_data": "SQL:SELECT CASE WHEN email NOT LIKE '%@%' THEN FALSE ELSE TRUE END AS is_valid FROM customers WHERE email = {request.email}"The post.columns array lists the fields the POST endpoint accepts. To make a
field mandatory, append a * to its name. The * is only a marker — the engine
strips it and uses the real column name everywhere.
"post": {
"enable_method": true,
"columns": ["name*", "email*", "phone"]
}Here name and email are required; phone is optional. A required field is
rejected when it is absent, null, an empty string, or the literal string
"null", returning:
400 Bad Request — Missing required field: name
Notes:
- A field is also treated as required (even without
*) when itscolumns[]definition has"nullable": falseand"auto_increment": false. The*suffix is the explicit way to enforce it for any column. - Empty datetime/timestamp values (
""or"null") are coerced to SQLNULLbefore the required check runs. - PUT (
put.columns) uses the exact same*convention for updates.
Placeholders are available inside hooks and formula values:
| Placeholder | Expands to |
|---|---|
{request.field} |
A value from the request body. Multipart form fields are supported; text that is valid JSON is parsed. Dotted paths work: {request.user.id}, {request.items.0.price}. |
{table[123].col} |
Subquery (SELECT col FROM table WHERE id = 123). |
{table[{request.id}].col} |
Subquery with a dynamic id taken from the request. |
Notes:
- For
PUT, the path parameter/{id}is not auto‑injected into hooks — includeidin the request body if a formula needs it. - For
POSTwith auto‑increment ids, the new id is not available via{request.*}. To reference it afterwards, use a custom id (columns[].function) or supplyidyourself. - Bindings are numeric/string‑inferred automatically. On PostgreSQL,
?placeholders are rewritten to$1, $2, …internally.
For a route <route> listed in routes.json (each method requires enable_method: true in its schema section):
| Method & path | Schema section | Description |
|---|---|---|
GET /<route>?col.op=value |
get |
Filtered read. |
POST /<route> |
post |
Create (multipart/form‑data; supports file uploads). Required fields are marked with a * suffix in post.columns — see §9. |
PUT /<route>/{id} |
put |
Update by id. |
DELETE /<route>/{id} |
del |
Delete (soft or hard per del.type_delete). |
PATCH /<route> |
patch |
Stored‑procedure / parameterized operation. |
TRACE /<route> |
trace |
Custom select + insert / upsert pipeline. |
POST /import/<route> |
post |
Bulk import (CSV / XLSX). See §13. |
GET /export/<route> |
get |
Export (CSV / XLSX). See §13. |
GET /validate/<route> |
— | Validate the entity JSON against the database. |
POST /generate/table/<route> |
— | Create the physical table (requires auto_generate: true; not for core tables). |
Core / system endpoints:
| Method & path | Description |
|---|---|
POST /login |
Authenticate, returns a JWT (see §12). |
POST /register |
Register a user (multipart). |
GET /roles |
List roles. |
GET /healthz |
Health check: { "status": "ok", "db": "up|down", "db_type": "…" }. Returns 503 if the DB is unreachable. |
GET /metrics |
Prometheus‑format metrics. |
GET /static/... |
Static files from LOC_STATIC (directory listing in debug mode). |
curl -X POST http://localhost:8080/login \
-H "Authorization: Basic $(printf 'admin:1234' | base64)"- Credentials are passed via HTTP Basic auth (
Basic base64(email:password)). - Success returns a JWT with claims such as
id,nm(name),rl(roles), and optionalcs(custom claim fromCUSTOME_JWT_QUERY). - Send it on every protected request:
Authorization: Bearer <token>.
The default admin (email admin) is seeded on first start; its generated password is printed to the console.
- Routes in
route_publics(plus/login,/register) are public. - All other routes require a valid
Bearertoken. - IPs/CIDRs in
WHITE_LIST_IPbypass token checks. - Fine‑grained, role‑/endpoint‑based rules can be defined in
LOC_CONFIG/rules.json(per‑methodpermission_id,allowed_fields, andifconditions such as$user.role/$user.id).
When routes.json defines a non‑default converter_token mapping, JWTs are issued by an external identity provider and Flexurio verifies their signature using the CONVERTER_JWT_* variables (§5). This is fail‑closed: with no verification key configured, converter‑token requests are rejected (unless CONVERTER_JWT_INSECURE_SKIP_VERIFY=true).
- Import —
POST /import/<route>with a multipart file. CSV and XLSX are supported; column headers must match the entity's insertable columns. Rows are inserted in batches (IMPORT_BATCH_SIZE). - Export —
GET /export/<route>?type=csv|xlsxreturns the route's data in the requested format (defaults to CSV; falls back to CSV if XLSX generation fails). The same filtering asGET /<route>applies.
Set "encrypt": true on a column to store its value encrypted at rest using ENCRYPT_KEY (AES‑GCM). The engine encrypts on write and decrypts on read transparently. Keep ENCRYPT_KEY secret and stable — rotating it requires re‑encrypting existing data.
- Structured logs cover endpoint registration and query execution; control verbosity with the
LOG_*,DEBUG, andLOGGINGvariables (§5). GET /healthzfor liveness/readiness probes.GET /metricsexposes Prometheus metrics.- An audit trail is written to
LOC_AUDIT. - Logs and static assets are served under
/static(e.g.GET /static/log/). Keep the audit log outsidestatic/so it is not publicly served.
Database backends are gated behind Cargo features so you can build a lean binary with only what you need.
[features]
default = ["mysql", "postgres", "sqlite", "mssql", "mongodb"]
mysql = ["sqlx/mysql", "sqlx/chrono"]
postgres = ["sqlx/postgres", "sqlx/chrono"]
sqlite = ["sqlx/sqlite", "sqlx/chrono"]
mssql = ["tiberius/chrono", "bb8"]
mongodb = ["dep:mongodb"]If you disable a backend but set DB_TYPE to it at runtime, the app exits with an error (e.g. mysql feature disabled).
# Only MySQL
cargo build --release --no-default-features --features mysql
# MySQL + SQLite
cargo build --release --no-default-features --features "mysql sqlite"
# Everything (default)
cargo build --release
# Build & run in one step
cargo run --release --no-default-features --features mysqlSmaller builds compile faster, produce smaller binaries, and remove unused code paths from production.
build.sh produces per‑database, per‑OS binaries with feature‑gated builds, and optionally signs/notarizes macOS artifacts when Apple credentials are present.
Use
./build.shorbash build.sh(notsh build.sh) — macOS ships Bash 3.2.
./build.sh [--db <list>] [--os <list>] [--arch <list>] [--help]--db—mysql,postgres,sqlite,all(defaultall).--os—macos,windows,linux,all(defaultall).--arch—x86_64,aarch64,all(filters after OS expansion, defaultall).
OS group expansion:
macos→x86_64-apple-darwin,aarch64-apple-darwinwindows→x86_64-pc-windows-gnulinux→x86_64-unknown-linux-gnu,aarch64-unknown-linux-gnu
Examples:
./build.sh # all DBs, all OS targets
./build.sh --db mysql # MySQL only, all OS
./build.sh --db mysql,sqlite --os macos # MySQL + SQLite for macOS (both arches)
./build.sh --db postgres --os macos --arch aarch64
./build.sh --db mysql --os windows,linux --arch x86_64Artifacts land in release/ as flx-nocode-<driver>-<target> (Windows adds .exe; signed macOS produces .pkg). With --db all (default) a single combined multi‑driver binary is emitted per target as flx-nocode-<target> for installer compatibility.
For each driver the script runs cargo build --release --target <triple> --no-default-features --features <driver>. macOS signing/notarization activates when APPLE_ID, APPLE_TEAM_ID, an app‑specific password, APPLE_IDENTITY, APPLE_IDENTITY_INS, PRIMARY_BUNDLE_ID, and KEYCHAIN_PROFILE are set; otherwise it just copies the binary.
| Symptom | Cause | Fix |
|---|---|---|
Exit: LOC_CONFIG not set |
Missing env var | Set LOC_CONFIG to a config profile path. |
Panic: Invalid routes.json / ROUTES NOT VALID |
Malformed routes.json |
Validate JSON; ensure at least one route. |
Panic: Cannot read entity file |
Route listed but entity/<route>.json missing |
Create the file or remove the route. |
| Duplicate table error | Two schemas share the same table value |
Rename one. |
401 Unauthorized |
Missing/invalid Authorization header |
Re‑login and send Bearer <token>. |
| Table not found | Table never created | POST /generate/table/<route> (needs auto_generate: true) or create it manually. |
<backend> feature disabled |
DB_TYPE points to a backend not compiled in |
Rebuild with that feature, or change DB_TYPE (§16). |
| Hooks not running | Used before/after keys |
Use pre_process / post_process with the SQL: prefix (§9). |
| Custom id insert fails | function_endpoint unreachable / bad response |
Endpoint must return 2xx JSON with the configured field; or clear function_endpoint to use MAX(id)+1 (§8). |
- Use long, random
SECRET_KEYandENCRYPT_KEY; keep them out of version control. - Rotate keys periodically (reissue tokens; re‑encrypt data if
ENCRYPT_KEYchanges). - Grant the database user least privilege.
- Keep the audit log (
LOC_AUDIT) outside the publicly servedstatic/directory. - Terminate TLS at a reverse proxy (nginx / traefik / Caddy).
- In converter‑token mode, always configure signature verification — avoid
CONVERTER_JWT_INSECURE_SKIP_VERIFY=truein production. - Validate any externally‑supplied formula inputs.
Contributing
- Fork and branch (
feat/<name>). - Make focused commits; keep example configs valid.
- Run
cargo build/cargo test. - Open a PR with a clear description and test notes.
License — see LICENSE (and LICENSE-AGPL).
Credits — Flexurio Engineering Team. Built with Rust + Actix Web.
- Configure
.env(DB_TYPE, URL,SECRET_KEY,ENCRYPT_KEY,LOC_CONFIG). - List routes in
routes.json; addentity/<route>.jsonschemas. - Run the binary. 4. (Optional)
POST /generate/table/<route>. 5.GET /validate/<route>. - Log in, then call the REST endpoints with
Authorization: Bearer <token>.
Happy building. 🚀