A post hook is your function that runs after an API, with the result in g.res.output. Write it in any of the languages of API Maker. It can reshape the answer, add to it, replace it, send something elsewhere, or refuse to answer. It lives at the same levels as a pre hook : instance, database, table, API, custom and system API, with optional groups.
In short
A post hook is your code that runs after an API, with its answer in g.res.output.
It can change the answer, add to it, or send something elsewhere.
It lives at the same levels as a pre hook : instance, database, table, one API.
Post hooks run from the narrowest level to the widest : the hooks of the API, then of the table, then of the database, then of the instance, each list top to bottom. The last one to run sees what every other one did.
In API Testing, the Post Hooks tab of an API of a table lists every post hook which runs after it, in that order : its own, then the hooks of its table, its database and its instance (marked Table, Database, Instance : they are changed in their categories of API Testing). In Git, every hook is a folder of its own.
A post hook is written in the language of its API, as every hook is : the code of an API and all its hooks are one language on one runtime.
import*asTfrom'types';import*asdbfrom'db-interfaces';asyncfunctionmain(g:T.IAMGlobal){if(!g.req.isApiRequestFromUser)return;// g.res.output : the answer of the API (data), changeable// g.res.statusCode, g.res.warnings, g.res.errors// g.req : the request as the API saw it, after the pre hooks// g.res.shared : what the pre hooks left for you}module.exports=main;
fromamimportGasyncdefmain(g:G):ifnotg.req.is_api_request_from_user:return# g.res.output : the answer of the API (data), changeable# g.res.status_code, g.res.warnings, g.res.errors# g.req : the request as the API saw it, after the pre hooks# g.res.shared : what the pre hooks left for you
<?phpfunctionmain(AmG$g){if(!$g->req->isApiRequestFromUser){returnnull;}// $g->res->output : the answer of the API (data), changeable// $g->res->statusCode, $g->res->warnings, $g->res->errors// $g->req : the request as the API saw it, after the pre hooks// $g->res->shared : what the pre hooks left for youreturnnull;}
filestaticclassApi{publicstaticasyncTask<object?>Main(Gg){if(!g.Req.IsApiRequestFromUser)returnnull;// g.Res.Output : the answer of the API (data), changeable// g.Res.StatusCode, g.Res.Warnings, g.Res.Errors// g.Req : the request as the API saw it, after the pre hooks// g.Res.Shared : what the pre hooks left for youreturnnull;}}
packagecodeimport"apimaker/am"funcMain(g*am.G)(any,error){if!g.Req.IsApiRequestFromUser(){returnnil,nil}// g.Res.Output() : the answer of the API (data), changeable with g.Res.SetOutput// g.Res.StatusCode(), g.Res.Warnings(), g.Res.Errors()// g.Req : the request as the API saw it, after the pre hooks// g.Res.Shared() : what the pre hooks left for youreturnnil,nil}
useam::{Errors,G,Value};pubfnmain(g:&mutG)->Result<Value,Errors>{if!g.req.is_api_request_from_user(){returnOk(Value::Null);}// g.res.output_mut() : the answer of the API (data), changeable// g.res.status_code(), g.res.warnings(), g.res.errors()// g.req : the request as the API saw it, after the pre hooks// g.res.shared() : what the pre hooks left for youOk(Value::Null)}
importam.G;classApi{staticObjectmain(Gg){if(!g.req.isApiRequestFromUser())returnnull;// g.res.getOutput() : the answer of the API (data), changeable with g.res.setOutput// g.res.getStatusCode(), g.res.getWarnings(), g.res.getErrors()// g.req : the request as the API saw it, after the pre hooks// g.res.getShared() : what the pre hooks left for youreturnnull;}}
importam.Gfunmain(g:G):Any?{if(!g.req.isApiRequestFromUser)returnnull// g.res.output : the answer of the API (data), changeable// g.res.statusCode, g.res.warnings, g.res.errors// g.req : the request as the API saw it, after the pre hooks// g.res.shared : what the pre hooks left for youreturnnull}
The hook
Effect
returns nothing
The next post hook runs, then the answer is sent.
changes g.res.output
The answer is what is in g.res.output at the end.
returns a value
That value becomes the answer and the remaining post hooks do not run.
throws
The request answers with the error, whatever the API did. The database change of the API is not rolled back by that.
A hook in any language other than TypeScript runs in the sandbox of its language. A hook in TypeScript runs inside API Maker when it needs nothing of a sandbox, else in its sandbox of Node.js or Bun ; the answer is the same. (runOnNativeProcess runs a hook on the machine of the server.) Every hook runs within the sandbox timeout of the call.
No hook runs when the answer comes from the cache. Calls from your own code skip hooks unless skipHookRunning: false.
import*asTfrom'types';asyncfunctionmain(g:T.IAMGlobal){if(!g.req.isApiRequestFromUser)return;constnow=Date.now();// milliseconds since 1970awaitg.sys.db.saveSingleOrMultiple({instance:'mongodb',database:'shop',collection:'audit_logs',saveData:{api:g.req.reqInfo.apiInfo.id,by:g.req.auth.authAMDB?.email,at:now,took:now-g.res.shared.startedAt},});awaitg.sys.system.emitEvent('order-placed',g.res.output);}module.exports=main;
importtimefromamimportGasyncdefmain(g:G):ifnotg.req.is_api_request_from_user:returnnow=int(time.time()*1000)# milliseconds since 1970awaitg.sys.db.save_single_or_multiple('mongodb','shop','audit_logs',save_data={'api':g.req.req_info.apiInfo.id,'by':(g.req.auth.authAMDBor{}).get('email'),'at':now,'took':now-g.res.shared.startedAt,},)awaitg.sys.system.emit_event('order-placed',g.res.output)
<?phpfunctionmain(AmG$g){if(!$g->req->isApiRequestFromUser){returnnull;}$now=(int)(microtime(true)*1000);// milliseconds since 1970$g->sys->db->saveSingleOrMultiple('mongodb','shop','audit_logs',saveData:['api'=>$g->req->reqInfo->apiInfo->id,'by'=>$g->req->auth->authAMDB?->email,'at'=>$now,'took'=>$now-$g->res->shared->startedAt,]);$g->sys->system->emitEvent('order-placed',$g->res->output);returnnull;}
filestaticclassApi{publicstaticasyncTask<object?>Main(Gg){if(!g.Req.IsApiRequestFromUser)returnnull;varnow=DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();// milliseconds since 1970awaitg.Sys.Db.SaveSingleOrMultiple(newSave("mongodb","shop","audit_logs",new{api=g.Req.ReqInfo.GetObject("apiInfo").GetString("id"),by=g.Req.Auth.GetObject("authAMDB").GetString("email"),at=now,took=now-g.Res.Shared.GetInt64("startedAt"),}));awaitg.Sys.System.EmitEvent("order-placed",g.Res.Output?.DeepClone());returnnull;}}
packagecodeimport("time""apimaker/am")funcMain(g*am.G)(any,error){if!g.Req.IsApiRequestFromUser(){returnnil,nil}now:=time.Now().UnixMilli()// milliseconds since 1970_,err:=g.Sys.Db.SaveSingleOrMultiple(am.Save{Instance:"mongodb",Database:"shop",Collection:"audit_logs",SaveData:am.M{"api":g.Req.ReqInfo().Map("apiInfo").String("id"),"by":g.Req.Auth().Map("authAMDB").Get("email"),"at":now,"took":now-g.Res.Shared().Int("startedAt"),},})iferr!=nil{returnnil,err}_,err=g.Sys.System.EmitEvent("order-placed",g.Res.Output())returnnil,err}
usestd::time::{SystemTime,UNIX_EPOCH};useam::{Errors,G,Save,Value,json};pubfnmain(g:&mutG)->Result<Value,Errors>{if!g.req.is_api_request_from_user(){returnOk(Value::Null);}letnow=SystemTime::now().duration_since(UNIX_EPOCH).map_or(0,|since|since.as_millis()asi64);// milliseconds since 1970g.sys.db.save_single_or_multiple(Save::new("mongodb","shop","audit_logs",json!({"api":g.req.req_info()["apiInfo"]["id"],"by":g.req.auth()["authAMDB"]["email"],"at":now,"took":now-g.res.shared()["startedAt"].as_i64().unwrap_or(now),}),))?;g.sys.system.emit_event("order-placed",g.res.output().clone(),&[])?;Ok(Value::Null)}
importam.G;importam.JsonObject;importam.Save;classApi{staticObjectmain(Gg){if(!g.req.isApiRequestFromUser())returnnull;longnow=System.currentTimeMillis();// milliseconds since 1970g.sys.db.saveSingleOrMultiple(newSave("mongodb","shop","audit_logs",JsonObject.of("api",g.req.getReqInfo().getObject("apiInfo").getString("id"),"by",g.req.getAuth().getObject("authAMDB").get("email"),"at",now,"took",now-g.res.getShared().getLong("startedAt"))));g.sys.system.emitEvent("order-placed",g.res.getOutput());returnnull;}}
importam.Gimportam.Savefunmain(g:G):Any?{if(!g.req.isApiRequestFromUser)returnnullvalnow=System.currentTimeMillis()// milliseconds since 1970g.sys.db.saveSingleOrMultiple(Save("mongodb","shop","audit_logs",mapOf("api"tog.req.reqInfo.getObject("apiInfo").getString("id"),"by"tog.req.auth.getObject("authAMDB")["email"],"at"tonow,"took"tonow-g.res.shared.getLong("startedAt"),),),)g.sys.system.emitEvent("order-placed",g.res.output)returnnull}
For news to WebSocket clients, write nothing : the clients subscribed to the API get its answer after each successful call. Emit a WebSocket event yourself with g.sys.system.emitEventWS when the news needs another shape.
For an event after an API hit without code, attach the event to the API on the Events page (automatic trigger).
Post hooks do not run on get all by stream and query by stream : the rows are on their way while they are read. To reshape a stream, read it in a custom API with g.sys.db.getAllByStream.