Code Generation
Configuration for generating TypeScript/JavaScript client code for NpgsqlRest endpoints.
Overview
json
{
"NpgsqlRest": {
"ClientCodeGen": {
"Enabled": false,
"FilePath": null,
"FileOverwrite": true,
"IncludeHost": true,
"CustomHost": null,
"CommentHeader": "Simple",
"CommentHeaderIncludeComments": true,
"BySchema": true,
"IncludeStatusCode": true,
"CreateSeparateTypeFile": true,
"ImportBaseUrlFrom": null,
"ImportParseQueryFrom": null,
"IncludeParseUrlParam": false,
"IncludeParseRequestParam": false,
"HeaderLines": ["// autogenerated at {0}", ""],
"SkipRoutineNames": [],
"SkipFunctionNames": [],
"SkipPaths": [],
"SkipSchemas": [],
"DefaultJsonType": "any",
"UseRoutineNameInsteadOfEndpoint": false,
"ExportUrls": false,
"SkipTypes": false,
"UniqueModels": false,
"XsrfTokenHeaderName": null,
"ExportEventSources": true,
"CustomImports": [],
"CustomHeaders": {},
"IncludeSchemaInNames": true,
"ErrorExpression": "await response.json()",
"ErrorType": "{status: number; title: string; detail?: string | null} | undefined"
}
}
}General Settings
| Setting | Type | Default | Description |
|---|---|---|---|
Enabled | bool | false | Enable client code generation. |
FilePath | string | null | Output file path. Use {0} for schema name when BySchema is true. null to skip. |
FileOverwrite | bool | true | Overwrite existing files. |
BySchema | bool | true | Create separate files per PostgreSQL schema. |
IncludeSchemaInNames | bool | true | Include schema name in generated type names to avoid collisions. |
Host Configuration
| Setting | Type | Default | Description |
|---|---|---|---|
IncludeHost | bool | true | Include current host in URL prefix. |
CustomHost | string | null | Custom host prefix for URLs. |
Comment Headers
| Setting | Type | Default | Description |
|---|---|---|---|
CommentHeader | string | "Simple" | Comment header style: "None", "Simple", or "Full". |
CommentHeaderIncludeComments | bool | true | Include routine comments in header. |
Comment Header Styles
| Style | Description |
|---|---|
None | No comment header. |
Simple | Add routine name, parameters, and return values (default). |
Full | Add entire routine code as comment header. |
Response Options
| Setting | Type | Default | Description |
|---|---|---|---|
IncludeStatusCode | bool | true | Include status code in response: {status: response.status, response: model}. |
ErrorExpression | string | "await response.json()" | Expression to parse error responses. Only used when IncludeStatusCode is true. |
ErrorType | string | (see below) | TypeScript type for error responses. Only used when IncludeStatusCode is true. |
Default ErrorType: "{status: number; title: string; detail?: string | null} | undefined"
These options allow customization of error handling in generated code. Void functions and procedures also return the error object when IncludeStatusCode is true.
Type Generation
| Setting | Type | Default | Description |
|---|---|---|---|
CreateSeparateTypeFile | bool | true | Create separate {name}Types.d.ts file for global types. |
DefaultJsonType | string | "any" | Default TypeScript type for JSON types. |
SkipTypes | bool | false | Skip type generation for pure JavaScript output (changes .ts to .js). |
UniqueModels | bool | false | Merge models with same fields/types into one (reduces generated models). |
Import Configuration
| Setting | Type | Default | Description |
|---|---|---|---|
ImportBaseUrlFrom | string | null | Module to import baseUrl constant from. |
ImportParseQueryFrom | string | null | Module to import parseQuery function from. |
CustomImports | array | [] | Custom import statements (full expressions). |
Function Parameters
| Setting | Type | Default | Description |
|---|---|---|---|
IncludeParseUrlParam | bool | false | Include parseUrl: (url: string) => string parameter. |
IncludeParseRequestParam | bool | false | Include parseRequest: (request: RequestInit) => RequestInit parameter. |
Skip Options
| Setting | Type | Default | Description |
|---|---|---|---|
SkipRoutineNames | array | [] | Routine names to skip (without schema). |
SkipFunctionNames | array | [] | Generated function names to skip (without schema). |
SkipPaths | array | [] | URL paths to skip. |
SkipSchemas | array | [] | Schema names to skip. |
Export Options
| Setting | Type | Default | Description |
|---|---|---|---|
ExportUrls | bool | false | Export URLs as constants. |
ExportEventSources | bool | true | Export EventSource create functions for streaming events. |
UseRoutineNameInsteadOfEndpoint | bool | false | Use routine name instead of endpoint name for functions. |
Headers and Security
| Setting | Type | Default | Description |
|---|---|---|---|
CustomHeaders | object | {} | Custom headers added to each request. |
XsrfTokenHeaderName | string | null | XSRF token header name for anti-forgery (used in upload FORM POSTs). |
File Headers
| Setting | Type | Default | Description |
|---|---|---|---|
HeaderLines | array | ["// autogenerated at {0}", ""] | Header lines for generated files. {0} = timestamp. |
What Gets Generated
This section walks through what the generated TypeScript actually looks like for each setting that affects output shape. All examples below are taken verbatim from real projects.
Default Function Shape
For a PostgreSQL function like:
sql
create function public.who_am_i(
_user_id text default null,
_username text default null,
_email text default null
) returns table(user_id text, username text, email text)
language sql security definer as $$
select $1, $2, $3;
$$;
comment on function public.who_am_i is 'HTTP GET
@authorize
@user_parameters';Equivalent as a SQL file endpoint (sql/who-am-i.sql):
sql
/*
HTTP GET
@authorize
@user_parameters
@param $1 user_id text
@param $2 username text
@param $3 email text
*/
select $1 as user_id, $2 as username, $3 as email;The TypeScript client generator treats both sources identically — the same IWhoAmIRequest / IWhoAmIResponse shapes and whoAmI() function are produced regardless of whether the endpoint is a function or a SQL file.
The generated TypeScript client looks like this:
typescript
interface IWhoAmIRequest {
userId?: string | null;
username?: string | null;
email?: string | null;
}
interface IWhoAmIResponse {
userId: string | null;
username: string | null;
email: string | null;
}
export async function whoAmI(
request: IWhoAmIRequest
) : Promise<{
status: number,
response: IWhoAmIResponse,
error: {status: number; title: string; detail?: string | null} | undefined
}> {
const response = await fetch(baseUrl + "/api/who-am-i" + parseQuery(request), {
method: "GET"
});
return {
status: response.status,
response: response.ok ? await response.json() as IWhoAmIResponse : undefined!,
error: !response.ok && response.headers.get("content-length") !== "0"
? await response.json() as {status: number; title: string; detail?: string | null}
: undefined
};
}PostgreSQL parameter names (snake_case) are converted to camelCase. Optional parameters (those with DEFAULT) become ? properties. The IncludeStatusCode setting (default true) wraps every response in { status, response, error } — this is what makes error handling consistent across every call.
IncludeStatusCode: false — Direct Response
Set IncludeStatusCode: false to skip the wrapper:
typescript
export async function whoAmI(request: IWhoAmIRequest): Promise<IWhoAmIResponse> {
const response = await fetch(baseUrl + "/api/who-am-i" + parseQuery(request), {
method: "GET"
});
return await response.json() as IWhoAmIResponse;
}Errors throw or surface as runtime exceptions instead of being part of the return type. Use this if you have application-level error handling middleware.
CreateSeparateTypeFile: true — Type-Only Files
When true (default), interfaces are emitted into a sibling .d.ts file:
text
src/api/userApi.ts ← functions
src/api/userApiTypes.d.ts ← interfaces (type-only)The .d.ts file is pure type declarations:
typescript
//
// autogenerated file - do not edit
//
interface IWhoAmIRequest {
userId?: string | null;
username?: string | null;
email?: string | null;
}
interface IWhoAmIResponse {
user_id: string | null;
username: string | null;
email: string | null;
}Set CreateSeparateTypeFile: false to emit interfaces inline in the same file as the functions.
ExportUrls: true — URL Constants
When enabled, a URL builder for each endpoint is exported:
typescript
export const cancelComputeUrl = () => baseUrl + "/api/cancel-compute";
export const computeVisualizationUrl = (request: IComputeVisualizationRequest) =>
baseUrl + "/api/compute-visualization" + parseQuery(request);Useful when you need to construct a URL but don't want to make the request immediately — for <a href> links, <form action> attributes, or passing to a third-party library.
ExportEventSources: true — SSE Helpers
For endpoints with the @sse annotation, an EventSource constructor is exported:
typescript
export const createComputeVisualizationEventSource = (id: string = "") =>
new EventSource(baseUrl + "/api/compute-visualization/info?" + id);The optional id parameter scopes the event stream to a specific execution. See the SSE annotation for usage.
ImportBaseUrlFrom & ImportParseQueryFrom
By default, generated files include their own baseUrl constant and parseQuery helper. To share these across files, point them to a module that exports them:
jsonc
{
"ImportBaseUrlFrom": "$lib/urls",
"ImportParseQueryFrom": "$lib/urls"
}Generated files now import instead of inlining:
typescript
//
// autogenerated file - do not edit
//
import { baseUrl } from "$lib/urls";
import { parseQuery } from "$lib/urls";Where $lib/urls.ts is a file you maintain:
typescript
export const baseUrl = import.meta.env.VITE_API_BASE_URL ?? "";
export const parseQuery = (query: Record<string, any>) => "?" + Object.keys(query ?? {})
.map(key => {
const value = query[key] ?? "";
if (Array.isArray(value)) {
return value.map(s => s ? `${key}=${encodeURIComponent(s)}` : `${key}=`).join("&");
}
return `${key}=${encodeURIComponent(value as string)}`;
})
.join("&");This is the recommended pattern for SvelteKit / Next.js / Vite apps where baseUrl should come from environment variables.
Path Parameters
When endpoints use path parameters (e.g., @path /products/{p_id}), template literals are used in URLs:
typescript
export async function getProduct(request: { pId: number }) {
const response = await fetch(`${baseUrl}/products/${request.pId}`, {
method: "GET"
});
return { status: response.status, response: await response.json() };
}parseQuery is only emitted when at least one endpoint has actual query-string parameters. Endpoints with only path parameters skip the helper entirely.
UseRoutineNameInsteadOfEndpoint: true
By default, function names come from the URL path (kebab-case → camelCase): /api/who-am-i → whoAmI().
With UseRoutineNameInsteadOfEndpoint: true, function names come from the PostgreSQL routine name instead: public.who_am_i → whoAmI().
Useful when you customize URL paths via @path annotations but want function names that still match the SQL routine names. Combines well with IncludeSchemaInNames: false to drop schema prefixes from generated names.
BySchema: true — One File Per Schema
Default behavior. With FilePath: "./src/api/{0}Api.ts", the {0} placeholder is replaced with each schema name:
text
src/api/publicApi.ts ← from public schema
src/api/publicApiTypes.d.ts
src/api/billingApi.ts ← from billing schema
src/api/billingApiTypes.d.tsSet BySchema: false and use a fixed filename (no {0}) to emit a single combined file.
Example Configurations
Minimal (Examples Repo Style)
The simplest setup — one file per schema, everything else default:
jsonc
{
"NpgsqlRest": {
"ClientCodeGen": {
"Enabled": true,
"FilePath": "./src/{0}Api.ts"
}
}
}This is what every example in the examples repository uses.
Single JavaScript File (No Types)
Use this when you don't want TypeScript:
jsonc
{
"NpgsqlRest": {
"ClientCodeGen": {
"Enabled": true,
"FilePath": "./src/api/client.js",
"BySchema": false,
"SkipTypes": true,
"IncludeSchemaInNames": false
}
}
}SkipTypes: true removes all TypeScript syntax (interfaces, type annotations) so the file is valid JavaScript despite the .ts → .js extension.
Production SvelteKit / Vite Setup
Real-world configuration with shared baseUrl/parseQuery from a $lib alias, URL constants for use in templates, and routine-name-based function naming:
jsonc
{
"NpgsqlRest": {
"ClientCodeGen": {
"Enabled": true,
"FilePath": "./src/app/api/{0}Api.ts",
"FileOverwrite": true,
"IncludeHost": true,
"CommentHeader": "Simple",
"CommentHeaderIncludeComments": true,
"BySchema": true,
"IncludeStatusCode": true,
"CreateSeparateTypeFile": true,
"ImportBaseUrlFrom": "$lib/urls",
"ImportParseQueryFrom": "$lib/urls",
"DefaultJsonType": "string",
"UseRoutineNameInsteadOfEndpoint": true,
"ExportUrls": true,
"ExportEventSources": true,
"IncludeSchemaInNames": false,
"HeaderLines": [
"//",
"// autogenerated file - do not edit",
"//"
]
}
}
}What this gives you:
- One
*Api.ts+ one*ApiTypes.d.tsfile per schema in./src/app/api/ - Generated files import
baseUrlandparseQueryfrom$lib/urls(your own module) JSONPostgreSQL columns typed asstringinstead ofany— explicit casting at the call site- Function names match SQL routine names (good for grep / refactoring across SQL and TS)
- URL builder constants exported (
computeUrl(),loginUrl(), etc.) for use in<a href>, forms, and library integrations EventSourcefactory functions for any@sseendpoints- Schema name dropped from interface names (
ICancelComputeRequest, notIMathmoduleCancelComputeRequest)
With Custom Headers and Imports
For projects that need to add custom headers to every request or import external utilities into the generated files:
jsonc
{
"NpgsqlRest": {
"ClientCodeGen": {
"Enabled": true,
"FilePath": "./src/api/{0}Api.ts",
"ImportBaseUrlFrom": "@/config",
"ImportParseQueryFrom": "@/utils/query",
"CustomImports": [
"import { handleError } from '@/utils/errors';"
],
"CustomHeaders": {
"X-Client-Version": "\"1.0.0\"",
"X-Client-Platform": "\"web\""
},
"XsrfTokenHeaderName": "X-XSRF-TOKEN"
}
}
}Note the CustomHeaders value syntax — values are emitted as TypeScript expressions, so a literal string requires escaped quotes ("\"1.0.0\""). To use a dynamic value, write a JS expression: "() => localStorage.getItem('app-version')".
Related
- TSCLIENT annotation - Per-endpoint TypeScript client control
- Comment Annotations Guide - How annotations work
- Configuration Guide - How configuration works
Next Steps
- HTTP Files - Configure HTTP file generation
- OpenAPI - Configure OpenAPI specification generation
- NpgsqlRest Options - Configure general NpgsqlRest settings
See Also
- TSCLIENT - Per-endpoint TypeScript client control