Profiles / Programming
BW

Backend & web service developer

Typed scripts, execute / call / include, generated calls and URLs, parameters and logs.

10 recipes on this page · open in the interactive view

Recipes
  1. Publish a typed web service with its parameter and stack options
  2. Inspect a script and answer with a JSON object
  3. execute, call and include: which environment?
  4. Generate the calls of a service for the team
  5. Read server parameters and trace a run in the logs
  6. Signed webhook receiver with replay protection
  7. Versioned API behind one router, deployed with merge
  8. Health check endpoint for the load balancer
  9. Bulk import endpoint with a line by line report
  10. An authentication guard shared with include
needs your systems

Publish a typed web service with its parameter and stack options

A shop needs a POST service that computes a price with VAT. The script stores the VAT rate as a server parameter, creates the script with typed and documented parameters, logs each call, sets its stack and delay options, then calls it once. It changes the server, so it is not run here.

Pipeline
Script
admin
#1. The VAT rate is a server parameter;
if (parameter exist "SHOP_VAT_RATE") {
	parameter update "SHOP_VAT_RATE" "20";
	parameter set_description "SHOP_VAT_RATE" "VAT rate in percent used by the shop";
} {
	parameter add "SHOP_VAT_RATE" "20" "VAT rate in percent used by the shop" false;
};
#2. A POST script with typed parameters;
script create post "shop.price.vat" true 1
	(param
		(var "[net]" {type is_double [net]} "Net price" is_null:false is_empty:false "100")
		(var "[qty]" {type is_number [qty]} "Quantity" is_null:false is_empty:false "2")
	;)
	"Gross price of a quantity of items"
{
	-> "[rate]" (parameter get value "SHOP_VAT_RATE");
	-> "[gross]" (math round (* (* [net] [qty]) (+ 1 (/ [rate] 100))) 2);
	log write (concat "price " [net] " x " [qty]) "OK" "qty" [qty];
	[gross];
} "The gross amount";
#3. Stack and delay options;
script set stack "shop.price.vat.post" 0 4 true;
script set delay "shop.price.vat.post" 0 day {true;};
#4. First call;
execute "shop.price.vat.post" "[net]" 19.90 "[qty]" 3;
Result
This recipe uses your own connections (database, SFTP, SMTP...). Create them with the CM functions, then run it on your server.
Go further
  • Change the rate later with parameter update "SHOP_VAT_RATE" 5.5
  • Give the URL to the front-end team with script generate url "shop.price.vat.post"
executed on MentDB

Inspect a script and answer with a JSON object

Before wiring a front-end to a service, a developer checks that it exists, who may call it, which groups own it and what its parameters are. The script splits the name with atom functions, loads the definition, groups, users and parameters as JSON documents, then builds and edits the JSON answer with every insert, update and delete function.

Pipeline
Script
admin
#1. A script name is a dotted path ending with the method;
-> "[name]" "addition.post";
-> "[method]" (atom get_last [name] ".");
-> "[base]" (atom before_exclud [name] (atom size [name] ".") ".");
-> "[ok]" (and (script exist [name]) (script is granted [name] "admin"));
#2. Definition, groups, users and parameters as JSON;
json load "rx_def" (script get [name]);
json load by ref "rx_def" "/groups" "rx_grp";
-> "[admin]" (json load select "rx_usr" (script show users [name]) "/admin");
json load "rx_par" (script show params [name]);
#3. Build the answer, starting from a draft;
json load "rx_out" "{\"status\": \"draft\", \"tmp\": 0}";
json iobject "rx_out" "/" "script" [base] STR;
json iobject "rx_out" "/" "method" [method] STR;
json iobject "rx_out" "/" "granted" [ok] BOOL2;
json iobject "rx_out" "/" "threads" (json select "rx_def" "/nb_in_thread") INT;
json iobject "rx_out" "/" "groups" (json fields "rx_grp" "/") ARRAY;
json iobject "rx_out" "/" "params" "[]" ARRAY;
for (-> "[i]" 0) (< [i] (json count "rx_par" "/")) (++ "[i]") {
	json iarray "rx_out" "/params" (json select "rx_par" (concat "/[" [i] "]/name")) STR;
};
#4. Edit arrays and fields;
json sort "rx_out" "/params" "" DESC;
json iarray "rx_out" "/params" 0 "[v0]" STR;
json uarray "rx_out" "/params" 0 "[v9]" STR;
json darray "rx_out" "/params" 0;
json uobject "rx_out" "/" "status" "checked" STR;
json dobject "rx_out" "/" "tmp";
json iobject "rx_out" "/" "checks" (concat "[" (json is object "rx_out" "/") ", " (json is array "rx_out" "/params") ", " (json exist "rx_usr") "]") ARRAY;
json iobject "rx_out" "/" "loaded" (json show) ARRAY;
-> "[answer]" (json doc "rx_out");
json unload_all;
[answer];
Result
mentdb
{
  "loaded": [
    "rx_out",
    "rx_par",
    "rx_usr",
    "rx_def",
    "rx_grp"
  ],
  "checks": [
    1,
    1,
    1
  ],
  "method": "post",
  "threads": 1,
  "groups": [
    "mentdb"
  ],
  "params": [
    "[v2]",
    "[v1]"
  ],
  "script": "addition",
  "status": "checked",
  "granted": true
}
Go further
  • Build an HTML form from script show params: one field per name, with its example and its type
  • json unload "rx_def" frees one document, json unload_all frees all of them
executed on MentDB

execute, call and include: which environment?

The same script is run three ways to see what happens to the variables of the caller. execute runs it in a new environment, call and include run it in the caller's environment, so the parameters they pass overwrite the caller's variables. Finally parallel runs three executions at the same time.

Pipeline
Script
admin
#1. The caller has its own v1;
-> "[v1]" "mine";
-> "[r1]" (execute "addition.post" "[v1]" 10 "[v2]" 5);
-> "[after_execute]" [v1];
#2. call and include share the environment of the caller;
-> "[r2]" (call "addition.post" "[v1]" 20 "[v2]" 5);
-> "[after_call]" [v1];
-> "[r3]" (include "addition.post" "[v1]" 30 "[v2]" 5);
-> "[after_include]" [v1];
#3. Three executions in parallel;
json load "rx_par" (parallel {execute "addition.post" "[v1]" 1 "[v2]" 1;} {execute "addition.post" "[v1]" 2 "[v2]" 2;} {execute "addition.post" "[v1]" 3 "[v2]" 3;});
-> "[p]" (concat (json select "rx_par" "/[0]/result") " " (json select "rx_par" "/[1]/result") " " (json select "rx_par" "/[2]/result") ", status " (json select "rx_par" "/[2]/status"));
json unload "rx_par";
concat "execute: " [r1] ", v1 is still " [after_execute] [_n_] "call: " [r2] ", v1 is now " [after_call] [_n_] "include: " [r3] ", v1 is now " [after_include] [_n_] "parallel: " [p];
Result
mentdb
execute: 15, v1 is still mine
call: 25, v1 is now 20
include: 35, v1 is now 30
parallel: 2 4 6, status OK
Go further
executed on MentDB

Generate the calls of a service for the team

A new service must be documented for the front-end and the other MQL developers. The script generates its HTTP URL with the headers, the MQL code to call it, include it, include it from a template and put it in the stack, and the first line of the create, update, merge and execute requests used to copy it to another server.

Pipeline
Script
admin
#1. The service to document;
-> "[s]" "addition.post";
#2. For the front-end: method, headers and URL;
-> "[doc]" (concat "== HTTP" [_n_] (script generate url [s]));
#3. For MQL developers;
-> "[doc]" (concat [doc] [_n_] "== call" [_n_] (script generate call [s]));
-> "[doc]" (concat [doc] [_n_] "== include, first line: " (atom get_first (script generate include [s]) [_n_]));
-> "[doc]" (concat [doc] [_n_] "== include from a template" [_n_] (script generate sub_include [s]));
-> "[doc]" (concat [doc] [_n_] "== stack, first line: " (atom get_first (script generate stack [s]) [_n_]));
#4. To copy the service to another server;
-> "[doc]" (concat [doc] [_n_] "== create: " (atom get_first (script generate create [s]) [_n_]) [_n_] "== update: " (atom get_first (script generate update [s]) [_n_]) [_n_] "== merge: " (atom get_first (script generate merge [s]) [_n_]) [_n_] "== execute: " (atom get_first (script generate execute [s]) [_n_]));
[doc];
Result
mentdb
== HTTP
Method
  post
Header
  x-token = (empty or not define the first time)
  x-user = admin (required the first time)
  x-password = ***** (required the first time)
URL
  https://localhost:9999/api/addition?x-token=&x-user=&v1=&v2=
== call
call "addition.post"
	"[v1]" "example ..."
	"[v2]" "example ..."
;
== include, first line: include "addition.post"
== include from a template
concat "include \"addition.post\"
	\"[v1]\" \"" (mql encode [v1]) "\"
	\"[v2]\" \"" (mql encode [v2]) "\"
;"
== stack, first line: stack_execute "2026-11-01 16:24:31" "addition.post"
== create: script create post "addition" false 1
== update: script update "addition.post" false 1
== merge: script merge "addition.post" false 1
== execute: execute "addition.post"
Go further
executed on MentDB

Read server parameters and trace a run in the logs

A script must behave differently in DEV and PROD and leave a trace. It branches on the server mode with mode, reads a parameter, its description and its private flag, generates the update and merge requests to copy it elsewhere, writes two traces, reads them back and counts today's errors in the log database.

Pipeline
Script
admin
#1. Branch on the server mode;
-> "[env]" (mode SERVER_MODE ("PROD") {"production"} ("DEV") {"development"} {"other"});
#2. A parameter, its description and its private flag;
-> "[p]" "SERVER_MODE";
-> "[info]" (concat [p] " = " (parameter get value [p]) ", private " (parameter is_private [p]) ", exists " (parameter exist [p]) [_n_] (parameter get description [p]));
#3. Requests to copy it to another server;
-> "[copy]" (concat (parameter generate update [p]) [_n_] (parameter generate merge [p]));
#4. Trace and read back;
log trace (concat "rx_demo start in " [env]);
log trace "rx_demo end";
-> "[last]" (log show 3);
#5. Errors of the day in the log database;
-> "[ko]" (log search "ko" null null null null (concat (date sysdate) " 00:00:00") (concat (date sysdate) " 23:59:59") DESC 1 50);
json load "rx_ko" (string regexp_extract [ko] "(\{.*)$" 1);
-> "[nb_ko]" (json count "rx_ko" "/data");
json unload "rx_ko";
concat "Server in " [env] [_n_] [info] [_n_] [copy] [_n_] "Last traces:" [_n_] (string replace [last] (date sysdate) "today") [_n_] "Errors today: " [nb_ko] ", current log id " (log current id) ", log retention " (log retention day) " days, archives of " (log archive size) " in " (log archive path);
Result
mentdb
Server in development
SERVER_MODE = DEV, private 0, exists 1
Mode of the server (DEV, TEST, PROD...), shown in the administration.
parameter update "SERVER_MODE" "DEV";
parameter merge "SERVER_MODE" "DEV";
Last traces:
today 16:24:31.718+0400: rx_demo start in development
today 16:24:31.718+0400: rx_demo end

Errors today: 0, current log id 0, log retention 50 days, archives of 30 in archives/logs
Go further
executed on MentDB

Signed webhook receiver with replay protection

A payment provider calls a POST web service with a JSON body and an HMAC-SHA256 signature. The service checks the signature with a private server parameter, ignores a retried event thanks to a node per event id, logs the payment and answers an HTTP-like code. The script plays a delivery, a retry and a forged body.

Pipeline
Script
admin
#1. The signing secret shared with the payment provider is a private parameter;
parameter add "tuto_p4l_webhook_secret" "whsec_tuto_2026" "Tutorial: secret of the payment webhook" false;
parameter set_private "tuto_p4l_webhook_secret" true;
#2. The receiver: check the signature, refuse replays, then process the event;
script create post "tuto.p4l.webhook.payment" false 1
	(param
		(var "[body]" {type is_json [body]} "Raw JSON body of the webhook" is_null:false is_empty:false "{}")
		(var "[signature]" {type is_matches_regex [signature] "[0-9a-f]{64}"} "Header X-Signature, HMAC-SHA256 in hex" is_null:false is_empty:false "00")
	;)
	"Receive a payment event from the provider"
{
	if (not (equal [signature] (string hmac_sha256 [body] (parameter get value "tuto_p4l_webhook_secret")))) {
		exception ("401") ("invalid signature");
	};
	json load "rx_p4l_evt" [body];
	-> "[id]" (json select "rx_p4l_evt" "/id");
	-> "[amount]" (json select "rx_p4l_evt" "/amount");
	json unload "rx_p4l_evt";
	-> "[seen]" (concat "tuto_p4l_evt_" (string md5 [id]));
	if (node exist [seen]) {
		concat "{\"code\": 200, \"status\": \"duplicate ignored\", \"id\": \"" [id] "\"}";
	} {
		node create [seen];
		node iobject [seen] "/" "received_at" (date now) STR;
		log write (concat "payment " [id] " " [amount]) "OK" "event" [id];
		concat "{\"code\": 201, \"status\": \"processed\", \"id\": \"" [id] "\", \"amount\": " [amount] "}";
	};
} "JSON with the HTTP code";
#3. The provider sends an event, retries it, then an attacker forges one;
-> "[evt]" "{\"id\": \"evt_7781\", \"type\": \"payment.succeeded\", \"amount\": 49.90}";
-> "[good]" (string hmac_sha256 [evt] "whsec_tuto_2026");
-> "[out]" "";
for (-> "[i]" 1) (<= [i] 3) (++ "[i]") {
	try {
		-> "[r]" (execute "tuto.p4l.webhook.payment.post" "[body]" (if (== [i] 3) {(string replace [evt] "49.90" "4990")} {[evt]}) "[signature]" [good]);
	} {
		-> "[r]" (concat "{\"code\": " (string regexp_extract [err] "([0-9]{3}): ([^\n]*)$" 1) ", \"error\": \"" (string regexp_extract [err] "([0-9]{3}): ([^\n]*)$" 2) "\"}");
	} "[err]";
	-> "[out]" (concat [out] "delivery " [i] ": " [r] [_n_]);
};
#4. Cleanup of the tutorial objects;
-> "[private]" (parameter is_private "tuto_p4l_webhook_secret");
node delete (concat "tuto_p4l_evt_" (string md5 "evt_7781"));
script delete "tuto.p4l.webhook.payment.post";
parameter remove "tuto_p4l_webhook_secret";
concat [out] "secret was private: " [private] ", still on the server: " (parameter exist "tuto_p4l_webhook_secret");
Result
mentdb
delivery 1: {"code": 201, "status": "processed", "id": "evt_7781", "amount": 49.9}
delivery 2: {"code": 200, "status": "duplicate ignored", "id": "evt_7781"}
delivery 3: {"code": 401, "error": "invalid signature"}
secret was private: 1, still on the server: 0
Go further
  • Answer 200 on a duplicate, not an error: providers retry until they get a 2xx.
  • Push the heavy processing to the stack with stack (date now) ... and answer 202 at once.
executed on MentDB

Versioned API behind one router, deployed with merge

Deploy v1 and v2 of a price API and a public router with script merge, which creates or updates a script without failing if it exists. The router builds the name of the version script, answers 404 if it does not exist, and a hotfix of v1 is merged again without duplicates.

Pipeline
Script
admin
#1. Deploy v1 with merge: created if missing, updated if it exists;
script merge "tuto.p4l.api.v1.price.get" false 1
	(param
		(var "[net]" {type is_double [net]} "Net price" is_null:false is_empty:false "100")
	;)
	"Price API v1: gross price as a number"
{
	math round (* [net] 1.2) 2;
} "A number";
#2. v2 answers JSON with the detail of the tax;
script merge "tuto.p4l.api.v2.price.get" false 1
	(param
		(var "[net]" {type is_double [net]} "Net price" is_null:false is_empty:false "100")
	;)
	"Price API v2: JSON with net, vat and gross"
{
	-> "[vat]" (math round (* [net] 0.2) 2);
	concat "{\"net\": " [net] ", \"vat\": " [vat] ", \"gross\": " (+ [net] [vat]) "}";
} "JSON";
#3. A router keeps one public URL and dispatches on the version;
script merge "tuto.p4l.api.price.get" false 1
	(param
		(var "[version]" {type is_matches_regex [version] "v[0-9]+"} "API version, v1 or v2" is_null:false is_empty:false "v2")
		(var "[net]" {type is_double [net]} "Net price" is_null:false is_empty:false "100")
	;)
	"Public price API with a version"
{
	-> "[target]" (concat "tuto.p4l.api." [version] ".price.get");
	if (not (script exist [target])) {exception ("404") (concat "unknown version " [version]);};
	execute [target] "[net]" [net];
} "The answer of the version";
#4. A hotfix of v1: merge again, the script is updated, not duplicated;
script merge "tuto.p4l.api.v1.price.get" false 1
	(param
		(var "[net]" {type is_double [net]} "Net price" is_null:false is_empty:false "100")
	;)
	"Price API v1: gross price as a number, rounded to the cent"
{
	math round (* [net] 1.2) 2;
} "A number";
#5. Clients of each version call the same router;
-> "[out]" "";
for (-> "[i]" 1) (<= [i] 3) (++ "[i]") {
	-> "[v]" (concat "v" [i]);
	try {-> "[r]" (execute "tuto.p4l.api.price.get" "[version]" [v] "[net]" 59.90);} {-> "[r]" (concat "error " (string regexp_extract [err] "([0-9]{3}: [^\n]*)$" 1));} "[err]";
	-> "[out]" (concat [out] [v] " -> " [r] [_n_]);
};
-> "[desc]" (json load select "rx_p4l_def" (script get "tuto.p4l.api.v1.price.get") "/desc");
script delete "tuto.p4l.api.v1.price.get";
script delete "tuto.p4l.api.v2.price.get";
script delete "tuto.p4l.api.price.get";
concat [out] "v1 after the hotfix: " [desc];
Result
mentdb
v1 -> 71.88
v2 -> {"net": 59.90, "vat": 11.98, "gross": 71.88}
v3 -> error 404: unknown version v3
v1 after the hotfix: Price API v1: gross price as a number, rounded to the cent
Go further
  • Put the merge commands in a deployment script: running it twice gives the same server.
  • script generate merge "tuto.p4l.api.v2.price.get" prints the merge command to copy a version to production.
executed on MentDB

Health check endpoint for the load balancer

A GET service answers the monitoring with a JSON health report: JVM and system memory, CPU, sessions, stack queue and errors, job scheduler, maintenance switches and version. Each problem is listed and the status becomes DEGRADED; the call uses a strict queue limit.

Pipeline
Script
admin
#1. A health endpoint for the load balancer and the monitoring;
script create get "tuto.p4l.health" false 1
	(param
		(var "[max_wait]" {type is_integer [max_wait] 5} "Max processes waiting in the stack before DEGRADED" is_null:false is_empty:false "50")
	;)
	"Health check of the server, as JSON"
{
	-> "[t0]" (date current_ms);
	json load "rx_p4l_health" "{\"checks\": {}, \"problems\": []}";
	#Memory of the JVM: used/allocated/max in MB;
	-> "[jvm]" (metric current mem jvm);
	-> "[mem_pct]" (math round (* (/ (atom get [jvm] 1 "/") (atom get [jvm] 3 "/")) 100) 1);
	json iobject "rx_p4l_health" "/checks" "jvm_mem_used_pct" [mem_pct] NUM;
	json iobject "rx_p4l_health" "/checks" "system_mem_available_pct" (math round (* (/ (metric current available mem) (metric total mem)) 100) 1) NUM;
	json iobject "rx_p4l_health" "/checks" "cpu_jvm_pct" (metric current cpu jvm) NUM;
	json iobject "rx_p4l_health" "/checks" "sessions" (count sessions) INT;
	json iobject "rx_p4l_health" "/checks" "stack_waiting" (stack count_wait) INT;
	json iobject "rx_p4l_health" "/checks" "stack_errors" (stack count_error) INT;
	json iobject "rx_p4l_health" "/checks" "scheduler" (job scheduler status) STR;
	#Services stopped by a maintenance switch;
	json load "rx_p4l_mnt" (string regexp_extract (maintenance get) "(\{.*)$" 1);
	json parse_obj "rx_p4l_mnt" "/" "[service]" "[off]" {
		if (equal [off] "true") {json iarray "rx_p4l_health" "/problems" (concat [service] " in maintenance") STR;};
	};
	json unload "rx_p4l_mnt";
	if (> [mem_pct] 90) {json iarray "rx_p4l_health" "/problems" "JVM memory above 90%" STR;};
	if (> (stack count_wait) [max_wait]) {json iarray "rx_p4l_health" "/problems" "stack queue too long" STR;};
	if (> (stack count_error) 0) {json iarray "rx_p4l_health" "/problems" "processes in error in the stack" STR;};
	json iobject "rx_p4l_health" "/" "status" (if (== (json count "rx_p4l_health" "/problems") 0) {"UP"} {"DEGRADED"}) STR;
	json iobject "rx_p4l_health" "/" "version" (version) STR;
	json iobject "rx_p4l_health" "/" "functions" (function count) INT;
	json iobject "rx_p4l_health" "/" "checked_at" (date now) STR;
	json iobject "rx_p4l_health" "/" "duration_ms" (- (date current_ms) [t0]) INT;
	-> "[answer]" (json doc "rx_p4l_health");
	json unload "rx_p4l_health";
	[answer];
} "JSON with status UP or DEGRADED";
#2. The monitoring calls it with a strict queue limit;
-> "[r]" (execute "tuto.p4l.health.get" "[max_wait]" 2);
script delete "tuto.p4l.health.get";
[r];
Result
mentdb
{
  "duration_ms": 3,
  "checks": {
    "scheduler": "Running...",
    "sessions": 3,
    "system_mem_available_pct": 27.1,
    "jvm_mem_used_pct": 11.8,
    "cpu_jvm_pct": 0.1,
    "stack_waiting": 3,
    "stack_errors": 0
  },
  "checked_at": "2026-11-01 18:25:25",
  "functions": 1608,
  "version": "4.2.3",
  "problems": [
    "stack queue too long"
  ],
  "status": "DEGRADED"
}
Go further
executed on MentDB

Bulk import endpoint with a line by line report

The front-end posts a CSV of customers to a POST service. Each line is cleaned (case, spaces), validated (e-mail, name, birth date in dd/MM/yyyy, plan in an enum) and either accepted in a normalised form or rejected with its line number and all its errors.

Pipeline
Script
admin
#1. A bulk import endpoint: the body is a CSV text, one customer per line;
script create post "tuto.p4l.customers.import" false 1
	(param
		(var "[csv]" {type is_varchar [csv] 100000} "CSV with header email;name;birth;plan" is_null:false is_empty:false "email;name;birth;plan")
	;)
	"Validate and import customers, answer a line by line report"
{
	json load "rx_p4l_imp" "{\"received\": 0, \"accepted\": [], \"rejected\": []}";
	-> "[n]" (atom size [csv] [_n_]);
	#Line 1 is the header;
	for (-> "[i]" 2) (<= [i] [n]) (++ "[i]") {
		-> "[row]" (atom get [csv] [i] [_n_]);
		-> "[email]" (string lower (atom get_lrtrim [row] 1 ";"));
		-> "[name]" (string capitalize_words (string lower (atom get_lrtrim [row] 2 ";")));
		-> "[birth]" (atom get_lrtrim [row] 3 ";");
		-> "[plan]" (string lower (atom get_lrtrim [row] 4 ";"));
		-> "[why]" "";
		if (not (type is_email [email])) {-> "[why]" (concat [why] "bad email, ");};
		if (is empty [name]) {-> "[why]" (concat [why] "name missing, ");};
		if (not (type is_valid_date [birth] "dd/MM/yyyy")) {-> "[why]" (concat [why] "bad birth date, ");};
		if (not (type is_enum [plan] "free,pro,team")) {-> "[why]" (concat [why] "unknown plan, ");};
		if (is empty [why]) {
			json iarray "rx_p4l_imp" "/accepted" (concat "{\"email\": \"" [email] "\", \"name\": \"" (string json_escape [name]) "\", \"birth\": \"" (date format [birth] "dd/MM/yyyy" "yyyy-MM-dd") "\", \"plan\": \"" [plan] "\"}") OBJ;
		} {
			json iarray "rx_p4l_imp" "/rejected" (concat "{\"line\": " [i] ", \"errors\": \"" (string substring [why] 0 (- (string length [why]) 2)) "\"}") OBJ;
		};
	};
	json uobject "rx_p4l_imp" "/" "received" (- [n] 1) INT;
	-> "[answer]" (json doc "rx_p4l_imp");
	json unload "rx_p4l_imp";
	[answer];
} "JSON report";
#2. The front-end posts a file of 5 customers;
-> "[file]" (concat "email;name;birth;plan" [_n_] "ANA.Silva@Mail.com ; ana SILVA ; 18/06/1990 ; Pro" [_n_] "bob@mail ; Bob Martin ; 01/02/1985 ; free" [_n_] "chloe@mail.com ; ; 31/04/1992 ; team" [_n_] "dev@corp.io ; Dev Patel ; 07/11/2001 ; gold" [_n_] "eva@corp.io;Eva Durand;29/02/2000;TEAM");
-> "[r]" (execute "tuto.p4l.customers.import.post" "[csv]" [file]);
script delete "tuto.p4l.customers.import.post";
#3. The answer summarised for the screen;
json load "rx_p4l_r" [r];
-> "[sum]" (concat (json count "rx_p4l_r" "/accepted") " accepted, " (json count "rx_p4l_r" "/rejected") " rejected of " (json select "rx_p4l_r" "/received"));
json unload "rx_p4l_r";
concat [sum] [_n_] [r];
Result
mentdb
2 accepted, 3 rejected of 5
{
  "rejected": [
    {
      "line": 3,
      "errors": "bad email"
    },
    {
      "line": 4,
      "errors": "name missing, bad birth date"
    },
    {
      "line": 5,
      "errors": "unknown plan"
    }
  ],
  "accepted": [
    {
      "name": "Ana Silva",
      "birth": "1990-06-18",
      "plan": "pro",
      "email": "ana.silva@mail.com"
    },
    {
      "name": "Eva Durand",
      "birth": "2000-02-29",
      "plan": "team",
      "email": "eva@corp.io"
    }
  ],
  "received": 5
}
Go further
  • Insert the accepted array in one SQL transaction with sql dml and sql commit.
  • Over a few thousand lines, push the file to the stack and answer an import id at once.
executed on MentDB

An authentication guard shared with include

Every service of an API starts with the same guard: it finds the client by the SHA-256 of its API key in a node, counts its calls against a daily quota, and raises 401 or 429. Because the guard runs with include, the client name it sets is visible in the service.

Pipeline
Script
admin
#1. API clients in a node: hash of the key, name, quota and calls of the day;
node create "tuto_p4l_clients";
node iobject "tuto_p4l_clients" "/" (string sha_256 "key-acme-42") (concat "{\"name\": \"ACME\", \"quota\": 2, \"calls\": 0}") OBJ;
#2. A guard shared by every service: run with include, so it writes in the caller environment;
script create exe "tuto.p4l.mw.guard" false 1
	(param
		(var "[api_key]" {true} "Value of the x-api-key header" is_null:true is_empty:true "key")
	;)
	"Authenticate the client and count its calls"
{
	-> "[h]" (concat "/" (string sha_256 [api_key]));
	if (is null (node select "tuto_p4l_clients" [h])) {exception ("401") ("unknown api key");};
	-> "[calls]" (+ (node select "tuto_p4l_clients" (concat [h] "/calls")) 1);
	if (> [calls] (node select "tuto_p4l_clients" (concat [h] "/quota"))) {exception ("429") ("quota of the day reached");};
	node uobject "tuto_p4l_clients" [h] "calls" [calls] INT;
	-> "[client]" (node select "tuto_p4l_clients" (concat [h] "/name"));
} "Sets [client] and [calls]";
#3. Two services start with the same guard line;
script create get "tuto.p4l.mw.stock" false 1 (param (var "[api_key]" {true} "x-api-key" is_null:true is_empty:true "key") (var "[sku]" {true} "Product" is_null:false is_empty:false "BK-1");) "Stock of a product"
{
	include "tuto.p4l.mw.guard.exe" "[api_key]" [api_key];
	concat "{\"client\": \"" [client] "\", \"call\": " [calls] ", \"sku\": \"" [sku] "\", \"stock\": 14}";
} "JSON";
script create get "tuto.p4l.mw.price" false 1 (param (var "[api_key]" {true} "x-api-key" is_null:true is_empty:true "key") (var "[sku]" {true} "Product" is_null:false is_empty:false "BK-1");) "Price of a product"
{
	include "tuto.p4l.mw.guard.exe" "[api_key]" [api_key];
	concat "{\"client\": \"" [client] "\", \"call\": " [calls] ", \"sku\": \"" [sku] "\", \"price\": 19.9}";
} "JSON";
#4. Calls: good key twice, then over quota, then a wrong key;
-> "[out]" "";
for (-> "[i]" 1) (<= [i] 4) (++ "[i]") {
	try {
		-> "[r]" (execute (if (== (math mod [i] 2) 1) {"tuto.p4l.mw.stock.get"} {"tuto.p4l.mw.price.get"}) "[api_key]" (if (== [i] 4) {"key-evil"} {"key-acme-42"}) "[sku]" "BK-1");
	} {
		-> "[r]" (concat "HTTP " (string regexp_extract [err] "([0-9]{3}): ([^\n]*)$" 1) " " (string regexp_extract [err] "([0-9]{3}): ([^\n]*)$" 2));
	} "[err]";
	-> "[out]" (concat [out] "call " [i] ": " [r] [_n_]);
};
#5. Cleanup;
script delete "tuto.p4l.mw.stock.get";
script delete "tuto.p4l.mw.price.get";
script delete "tuto.p4l.mw.guard.exe";
node delete "tuto_p4l_clients";
[out];
Result
mentdb
call 1: {"client": "ACME", "call": 1, "sku": "BK-1", "stock": 14}
call 2: {"client": "ACME", "call": 2, "sku": "BK-1", "price": 19.9}
call 3: HTTP 429 quota of the day reached
call 4: HTTP 401 unknown api key
Go further
  • Never store the key itself: a leak of the node gives only hashes.
  • Reset the counters every night with a job that runs node uobject on each client.

Also in Programming

MB
MQL beginner
Variables, operators, if, loops and your first try/exception: learn MQL step by step.
10 recipes
MD
MQL developer
Atom lists, data types, constants, translations, session variables and error patterns.
10 recipes
DT
Date & time developer
Calendars, business days, time zones, ISO and epoch, durations, quarters and week numbers.
10 recipes
SE
String & encoding developer
Cleaning, case styles, regex, URL/HTML/Base64 encodings, number bases and encryption.
10 recipes
DV
Data validation developer
IBAN, BIC, SIRET, Luhn, email, IP, UUID, JSON and date checks gathered in a validation report.
10 recipes
JD
JSON & document store developer
In-memory JSON documents, persistent nodes and sequences: build, walk, sort, clean up.
10 recipes
SD
Script developer
Full script lifecycle in the MQL editor: typed parameters, read back, generate, patch, deploy, copy, grant, delete.
10 recipes
EM
Execution modes
execute, call, include, sub_include, parallel, stack: which environment, which checks, which logs.
11 recipes
WS
Web service developer
A small REST resource with get, post, put and delete scripts: validation, JSON answers, codes, grants, URLs and API docs.
10 recipes
AD
Async developer
Asynchronous processing with the stack: push runs now or later, priorities and parallel limits, errors and replays, flows and cleanup.
10 recipes
SD
Scheduler developer
Cron jobs and delayed runs: add, resume, pause, update and remove jobs, prove they ran, and postpone stack runs with a delay condition.
10 recipes
ED
Event developer
Event-driven scripts: file watchers that push each file event to the stack, validation, archiving, errors and safe restarts.
10 recipes
CC
Configuration & connections
Parameters (private, described, locked, deployed), environment variables, sessions and connections used by your scripts.
12 recipes
VD
Versioning & deployment
Group your scripts, commit, compare, roll back, export, import and package a release.
10 recipes
DE
Debugging & error handling
Coded errors with try/exception, logs with correlation ids, timing, metrics and defensive checks.
10 recipes
SD
SQL developer in MQL
Connections, DDL and DML with sql encode, transactions, cursors, parse, and exports to JSON, CSV, Excel and PDF on a real local database. Run the recipes in order: the first one creates the tuto_db_h2 database.
10 recipes
CD
CRUD & data API developer
Generate SCRUD scripts from a table, patch and install them, write typed read and write APIs, check data quality and manage connections per environment. Uses the tuto_db_h2 database of the SQL developer recipes; the last recipe removes it.
10 recipes
WA
Web application developer
Create a web app, its host name, pages, ajax forms, menu and grants, then remove it all.
10 recipes
AI
Algorithms in MQL
Sorting, searching, recursion, data structures and number algorithms: MQL is a real programming language.
10 recipes
CF
Control flow
if, if force, case, switch, mode, for, while, repeat, break, continue, parallel and synchronized on real cases.
10 recipes
TM
The MQL language in depth
Data types and null, booleans, comments, variables and scope, eval and mql blocks, coded errors and try.
10 recipes
SU
Screen & UI developer (sajax)
Build the screens of MentDB web apps in MQL: Bootstrap grids, forms and every control, ajax buttons, alerts, refreshes and charts.
10 recipes