Skip to content

Latest commit

 

History

History
590 lines (463 loc) · 21.1 KB

File metadata and controls

590 lines (463 loc) · 21.1 KB
outline
2
3
title Code Generation Configuration
titleTemplate NpgsqlRest
description Generate TypeScript/JavaScript client code from your PostgreSQL API. Auto-create type-safe API clients with full type definitions from your database schema.
head
meta
name content
keywords
npgsqlrest codegen, postgresql typescript, generate api client, type-safe api, postgresql to typescript, auto-generate types
meta
property content
og:title
NpgsqlRest Code Generation Configuration
meta
property content
og:description
Generate TypeScript/JavaScript client code from your PostgreSQL API with full type definitions.
meta
property content
og:type
article

Code Generation

Configuration for generating TypeScript/JavaScript client code for NpgsqlRest endpoints.

Overview

{
  "NpgsqlRest": {
    "ClientCodeGen": {
      "Enabled": false,
      "FilePath": null,
      "FileOverwrite": true,
      "IncludeHost": true,
      "CustomHost": null,
      "CommentHeader": "Simple",
      "CommentHeaderIncludeComments": true,
      "BySchema": true,
      "IncludeStatusCode": true,
      "CreateSeparateTypeFile": true,
      "ExportTypes": false,
      "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",
      "OmitAutomaticParameters": false
    }
  }
}

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.
ExportTypes bool false Emit interfaces with the export keyword so they can be imported by other modules. When true and CreateSeparateTypeFile is true, the separate type file becomes an importable module {name}Types.ts (instead of an ambient {name}Types.d.ts) and the client file imports the named types from it. No effect when SkipTypes is true.
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).
OmitAutomaticParameters bool false Omit server-filled parameters from the generated request interface, query string, and body. See OmitAutomaticParameters: true.

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:

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):

/*
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:

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:

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.

OmitAutomaticParameters: true

::: tip New in 3.18.2 OmitAutomaticParameters was added in 3.18.2 (also available on the HTTP File and OpenAPI generators). Default is false, so generated output is unchanged unless you opt in. :::

Some parameters are filled by the server, so a value passed from the client would simply be ignored — emitting them as settable request properties is misleading. When true, such a parameter is dropped from the generated request interface, the query string, and the body when it is automatic and optional. "Automatic" covers:

For a function whose only client-settable parameter is query, with an HTTP Custom Type field responseBody filled server-side:

// OmitAutomaticParameters: false (default) — responseBody appears even though the server overrides it
interface ISearchRequest {
    query?: string | null;
    responseBody?: string | null;
}

// OmitAutomaticParameters: true — only the real input remains
interface ISearchRequest {
    query?: string | null;
}

When every parameter is automatic, the request shape collapses entirely — the generated function takes no request argument:

export async function ping(): Promise<{ status: number; response: IPingResponse; /* ... */ }> {
    const response = await fetch(baseUrl + "/api/ping", { method: "GET" });
    // ...
}

CreateSeparateTypeFile: true — Type-Only Files

When true (default), interfaces are emitted into a sibling .d.ts file:

src/api/userApi.ts        ← functions
src/api/userApiTypes.d.ts ← interfaces (type-only)

The .d.ts file is pure type declarations:

//
// 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.

ExportTypes: true — Importable Interfaces

By default, interfaces are emitted as plain interface declarations. That makes them module-private when inlined (CreateSeparateTypeFile: false) and ambient/global when in the separate .d.ts file — in neither case can another module import them. Set ExportTypes: true to emit them as export interface so they can be imported.

Inline (CreateSeparateTypeFile: false) — interfaces and functions share one file, with the interfaces now exported:

export interface ISearchProductsRequest {
    query?: string | null;
    maxPrice?: number | null;
}

export interface ISearchProductsResponse {
    id: number | null;
    name: string | null;
    price: number | null;
}

export async function searchProducts(
    request: ISearchProductsRequest
) : Promise<ApiResult<ISearchProductsResponse[]>> {
    // ...
}

Separate file (CreateSeparateTypeFile: true) — the type file becomes an importable module {name}Types.ts (not an ambient {name}Types.d.ts), and the client file imports the named types from it:

src/api/searchProducts.ts        ← functions + `import type { ... } from "./searchProductsTypes"`
src/api/searchProductsTypes.ts   ← `export interface ...`
// searchProducts.ts
import type { ISearchProductsRequest, ISearchProductsResponse } from "./searchProductsTypes";
const baseUrl = "";
// ...

ExportTypes has no effect when SkipTypes is true (no types are generated). Defaulting to false keeps existing output unchanged.

ExportUrls: true — URL Constants

When enabled, a URL builder for each endpoint is exported:

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:

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:

{
  "ImportBaseUrlFrom": "$lib/urls",
  "ImportParseQueryFrom": "$lib/urls"
}

Generated files now import instead of inlining:

//
// autogenerated file - do not edit
//
import { baseUrl } from "$lib/urls";
import { parseQuery } from "$lib/urls";

Where $lib/urls.ts is a file you maintain:

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:

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-iwhoAmI().

With UseRoutineNameInsteadOfEndpoint: true, function names come from the PostgreSQL routine name instead: public.who_am_iwhoAmI().

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:

src/api/publicApi.ts       ← from public schema
src/api/publicApiTypes.d.ts
src/api/billingApi.ts      ← from billing schema
src/api/billingApiTypes.d.ts

Set 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:

{
  "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:

{
  "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:

{
  "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.ts file per schema in ./src/app/api/
  • Generated files import baseUrl and parseQuery from $lib/urls (your own module)
  • JSON PostgreSQL columns typed as string instead of any — 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
  • EventSource factory functions for any @sse endpoints
  • Schema name dropped from interface names (ICancelComputeRequest, not IMathmoduleCancelComputeRequest)

With Custom Headers and Imports

For projects that need to add custom headers to every request or import external utilities into the generated files:

{
  "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

Next Steps

See Also

  • TSCLIENT - Per-endpoint TypeScript client control