| outline |
|
||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| title | PROXY_OUT Annotation | ||||||||||||||||||||||||||||
| titleTemplate | NpgsqlRest | ||||||||||||||||||||||||||||
| description | Execute PostgreSQL function first, then forward the result to an upstream service. Build post-processing pipelines with PDF rendering, ML inference, and more. | ||||||||||||||||||||||||||||
| head |
|
::: info Also known as
forward_proxy (with or without @ prefix)
:::
::: tip Available since version 3.11.0 :::
Execute the PostgreSQL function first, then forward its result body to an upstream service. The upstream response is returned to the client.
@proxy_out
@proxy_out [ host_url ]
@proxy_out [ http_method ]
@proxy_out [ http_method ] [ host_url ]
The proxy_out annotation reverses the flow of the existing proxy annotation. Instead of forwarding the incoming request to upstream, proxy_out forwards the outgoing function result.
This enables a common pattern where business logic in PostgreSQL prepares a payload, and an external service performs processing that PostgreSQL cannot do — PDF rendering, image processing, ML inference, email sending, etc.
Client Request → NpgsqlRest
→ Execute PostgreSQL function
→ Forward function result as request body to upstream service
→ Append the original request path and query string to the upstream host
→ Return upstream response to client
The client-facing HTTP method and the upstream HTTP method are independent — the client can send a GET while the upstream receives a POST.
The upstream URL is built the same way as for proxy — the incoming request path and query string are appended to the host:
target URL = host + incoming request path + incoming query string
The difference from proxy is only the direction of the body: proxy_out sends the function's result as the request body to the upstream, whereas proxy sends the incoming request body.
create function generate_report(report_id int)
returns json
language plpgsql as $$
begin
return json_build_object(
'title', 'Monthly Report',
'data', (select json_agg(row_to_json(t)) from sales t where month = report_id)
);
end;
$$;
comment on function generate_report(int) is 'HTTP GET
@proxy_out POST https://render-service.internal/render';Equivalent as a SQL file endpoint (sql/generate-report.sql):
/*
HTTP GET
@proxy_out POST https://render-service.internal/render
@param $1 report_id
*/
select json_build_object(
'title', 'Monthly Report',
'data', (select json_agg(row_to_json(t)) from sales t where month = $1)
);The client calls GET /api/generate-report/?reportId=3. The server:
- Executes
generate_report(3)in PostgreSQL. - Takes the returned JSON and POSTs it to
https://render-service.internal/render/api/generate-report/?reportId=3(original query string forwarded). - Returns the upstream response (e.g., a rendered PDF) directly to the client with the upstream's content-type and status code.
Uses the host from ProxyOptions.Host configuration:
-- function form
comment on function my_func() is '@proxy_out';-- sql/my-func.sql (SQL file form)
-- @proxy_out
select my_func();Override the default host:
-- function form
comment on function my_func() is 'HTTP GET
@proxy_out POST https://my-other-service.internal';-- sql/my-func.sql (SQL file form)
/*
HTTP GET
@proxy_out POST https://my-other-service.internal
*/
select my_func();Specify which HTTP method to use for the upstream request (uses default host from configuration):
-- function form
comment on function my_func() is 'HTTP GET
@proxy_out PUT';-- sql/my-func.sql (SQL file form)
/*
HTTP GET
@proxy_out PUT
*/
select my_func();The client sends GET, but the upstream receives PUT with the function's result as the body.
Specify both HTTP method and host:
-- function form
comment on function my_func() is 'HTTP GET
@proxy_out POST https://render-service.internal/render';-- sql/my-func.sql (SQL file form)
/*
HTTP GET
@proxy_out POST https://render-service.internal/render
*/
select my_func();Use a relative path starting with / to forward the function result to another endpoint on the same server:
-- function form
comment on function my_func() is 'HTTP GET
@proxy_out POST /api/internal-processor';-- sql/my-func.sql (SQL file form)
/*
HTTP GET
@proxy_out POST /api/internal-processor
*/
select my_func();Self-referencing calls bypass the HTTP stack entirely — the target endpoint handler is invoked directly in-process with zero network overhead.
The target URL follows the same resolution rules as proxy:
- Annotation URL takes priority — if provided (absolute or relative), the global
ProxyOptions.Hostis ignored. - Global
ProxyOptions.Hostis used only when the annotation has no URL. - A relative path (starting with
/) always creates an internal self-call, regardless ofProxyOptions.Host.
The original client request path and query string are both appended to the upstream host as-is (host + path + query). This lets the upstream receive the same path and parameters that were used to invoke the function:
create function generate_report(p_format text, p_id int)
returns json
language plpgsql as $$
begin
return json_build_object('id', p_id, 'data', 'report');
end;
$$;
comment on function generate_report(text, int) is 'HTTP GET
@proxy_out POST';With ProxyOptions.Host = "https://api.example.com", calling GET /api/generate-report/?pFormat=pdf&pId=123 executes the function, then POSTs the result body to https://api.example.com/api/generate-report/?pFormat=pdf&pId=123 — both the path and query string are appended.
To send the result to a fixed upstream path instead, put it in the annotation host (e.g. @proxy_out POST https://api.example.com/render, which forwards to https://api.example.com/render/api/generate-report/?...), or change the endpoint path with HTTP <method> <path>.
::: tip Self-calls are the exception
For a relative self-call (host starting with /, e.g. @proxy_out POST /api/processor), the annotation path is the full target and the incoming request path is not appended.
:::
- If the function fails (database error, exception), the error is returned directly to the client — the proxy call is never made.
- If the upstream fails (5xx, timeout, connection error), the upstream's error status and body are forwarded to the client (502 for connection errors, 504 for timeouts).
Prepare data in PostgreSQL and render it as a PDF via an external service:
create function invoice_pdf(invoice_id int)
returns json
language plpgsql as $$
begin
return json_build_object(
'invoice_number', invoice_id,
'items', (select json_agg(row_to_json(i)) from invoice_items i where i.invoice_id = invoice_pdf.invoice_id),
'total', (select sum(amount) from invoice_items where invoice_items.invoice_id = invoice_pdf.invoice_id)
);
end;
$$;
comment on function invoice_pdf(int) is 'HTTP GET
@proxy_out POST https://pdf-service.internal/render';Send prepared feature data to an ML service:
create function predict_churn(customer_id int)
returns json
language plpgsql as $$
begin
return (
select json_build_object(
'features', json_build_object(
'total_orders', count(*),
'last_order_days', extract(day from now() - max(order_date)),
'avg_order_value', avg(total)
)
)
from orders
where orders.customer_id = predict_churn.customer_id
);
end;
$$;
comment on function predict_churn(int) is 'HTTP GET
@proxy_out POST https://ml-service.internal/predict/churn';Prepare email content in PostgreSQL and send via an email service:
create function send_welcome_email(user_id int)
returns json
language plpgsql as $$
declare
u record;
begin
select * into u from users where id = user_id;
return json_build_object(
'to', u.email,
'subject', 'Welcome to Our Platform',
'body', format('Hello %s, welcome!', u.display_name)
);
end;
$$;
comment on function send_welcome_email(int) is 'HTTP POST
@proxy_out POST https://email-service.internal/send';The TypeScript client generator (NpgsqlRest.TsClient) recognizes proxy_out endpoints and generates functions that return the raw Response object. Since the actual response comes from the upstream proxy service (not from the PostgreSQL function's return type), the generated function returns Promise<Response>:
// Generated for a proxy_out endpoint
export async function generateReport() : Promise<Response> {
const response = await fetch(baseUrl + "/api/generate-report", {
method: "GET",
});
return response;
}This allows the caller to handle the response appropriately (.json(), .blob(), .text(), etc.).
Uses the same ProxyOptions configuration as the existing proxy annotation. ProxyOptions.Enabled must be true:
{
"NpgsqlRest": {
"ProxyOptions": {
"Enabled": true,
"Host": "https://api.example.com",
"DefaultTimeout": "30 seconds"
}
}
}See Proxy Options for complete configuration reference.
- Proxy Endpoints Guide — the full walkthrough
- PROXY - Forward incoming requests to upstream (reverse proxy)
- Proxy Options - Proxy configuration reference
- HTTP - Expose function as HTTP endpoint
- Proxy Config - Configure proxy options and settings