Generate framework-agnostic TypeScript services and models from GraphQL schemas using the native Fetch API.
Typed queries, mutations & subscriptions, generated straight from your schema (SDL). Runs on Node, browsers, Vite, SvelteKit, Next.js, Bun, Deno, and any TS runtime that ships fetch. No runtime dependencies in the generated code.
- NPM Package: @metaengine/graphql-fetch
- NuGet Package: MetaEngine.TypeScript.GraphQL.Fetch
- Website: metaengine.eu/packages/graphql-fetch
- ✅ Framework-agnostic — works in any TS runtime with
fetch - ✅ Native Fetch API — zero runtime dependencies in generated code
- ✅ Queries, mutations & subscriptions — typed operations generated straight from SDL, with a shared
executeGraphQL<T>helper - ✅ Reusable fragments — opt into named fragment spreads with
--fragments - ✅
@oneOfinputs — idiomatic tagged-union input types with--one-of-inputs - ✅ Custom scalar mapping — well-known scalars resolve to idiomatic TS types; override any scalar with
--custom-scalar - ✅ Production-ready — Bearer auth, timeouts, custom headers, retries, middleware, all via CLI flags
- ✅ Smart error handling — opt into HTTP-status-based routing (
--error-handling) - ✅ Result pattern — opt into
ApiResult<T>for structured error handling without exceptions - ✅ Vite + SvelteKit —
--import-meta-envforimport.meta.envaccess - ✅ Tree-shakeable — separate file per model and per operation type
npm install --save-dev @metaengine/graphql-fetchOr use directly with npx:
npx @metaengine/graphql-fetch <input> <output>- Node.js 18.0 or later
- .NET 8.0 or later runtime (Download)
- A TS runtime that supports
fetch(Node 18+, all modern browsers, Bun, Deno)
npx @metaengine/graphql-fetch schema.graphql ./src/api \
--documentation \
--fragmentsnpx @metaengine/graphql-fetch schema.graphql ./src/api \
--bearer-auth API_TOKEN \
--timeout 30 \
--retries 3 \
--custom-header X-Tenant-ID=TENANT_ID \
--error-handlingAdd to your package.json:
{
"scripts": {
"generate:api": "metaengine-graphql-fetch schema.graphql ./src/api --fragments --error-handling"
}
}Then run:
npm run generate:apiVite / SvelteKit (uses import.meta.env):
npx @metaengine/graphql-fetch schema.graphql ./src/api \
--import-meta-env \
--base-url-env VITE_API_URLNext.js:
npx @metaengine/graphql-fetch schema.graphql ./src/api \
--base-url-env NEXT_PUBLIC_API_URLPlain Node / Bun / Deno:
npx @metaengine/graphql-fetch schema.graphql ./src/api \
--base-url-env API_BASE_URL# Reusable fragments + tagged-union @oneOf inputs
npx @metaengine/graphql-fetch schema.graphql ./src/api \
--fragments \
--one-of-inputs
# Map custom scalars to idiomatic TS types
npx @metaengine/graphql-fetch schema.graphql ./src/api \
--custom-scalar DateTime=string \
--custom-scalar UUID=string| Option | Description | Default |
|---|---|---|
--fragments |
Emit reusable named fragments for object-type selections | false |
--one-of-inputs |
Generate idiomatic @oneOf input types (tagged-union inputs) |
false |
--custom-scalar <Scalar=target> |
Map a GraphQL custom scalar to a TS type. Repeatable. See Custom scalar mappings | - |
--base-url-env <name> |
Environment variable name for base URL | API_BASE_URL |
--import-meta-env |
Use import.meta.env for env access (Vite, SvelteKit) |
false |
--result-pattern |
Return ApiResult<T> instead of T for structured error handling |
false |
--middleware |
Emit middleware hooks (onRequest, onResponse, onError) in client |
false |
--error-handling |
Smart error handling based on HTTP status semantics | false |
--retries <max-attempts> |
Enable retries with exponential backoff (status codes 429, 503) | - |
--bearer-auth <env-var-name> |
Bearer token from env var (adds Authorization: Bearer <token>) |
- |
--timeout <seconds> |
Request timeout in seconds for all operations | - |
--custom-header <header=envVarName> |
Static header from env var. Repeatable. | - |
--documentation |
Generate JSDoc comments from SDL descriptions | false |
--date-transformation |
Convert Date-typed scalar fields (e.g. DateTime) in responses to Date objects |
false |
--options-threshold <n> |
Parameter count for options object | 4 |
--service-suffix <suffix> |
Service naming suffix | Service |
--types-barrel |
Emit an index.ts barrel per folder plus a root index.ts re-exporting everything |
false |
--clean |
Clean output directory (remove files not in generation) | false |
--verbose |
Enable verbose logging | false |
--help, -h |
Show help message | - |
output/
├── models/ # One file per type (object, input, enum, union)
│ ├── user.ts # export interface User { ... }
│ ├── role.ts # export type Role = 'ADMIN' | 'MEMBER' | ...
│ ├── feed-item.ts # union type alias
│ ├── create-post-input.ts # input object
│ ├── scalars.ts # custom scalar type aliases
│ └── ...
├── services/ # One file per root operation type
│ ├── query.service.ts # query operations
│ ├── mutation.service.ts # mutation operations
│ ├── subscription.service.ts # subscription operations
│ └── fragments.ts # (with --fragments) reusable named fragments
├── client.ts # ApiClient, createClient, getDefaultClient, executeGraphQL<T>
└── errors.ts # GraphQLError / HttpError helpers
Each operation collapses to a one-line call to the shared executeGraphQL<T> helper in client.ts, which owns the POST /graphql request and the data/errors envelope handling. Query strings are hoisted to module-scope constants.
npx @metaengine/graphql-fetch schema.graphql ./src/api --fragmentsEmits services/fragments.ts with one named fragment per object type. Operation selections reference fragment spreads (...UserFields) instead of repeating field sets, keeping generated query strings DRY.
npx @metaengine/graphql-fetch schema.graphql ./src/api --one-of-inputsRenders @oneOf input types (October-2021 GraphQL spec) as idiomatic TypeScript discriminated unions where exactly one member may be set:
export type PostFilter =
| { byAuthor: string; byTag?: never; byTitle?: never }
| { byTag: string; byAuthor?: never; byTitle?: never }
| { byTitle: string; byAuthor?: never; byTag?: never };Without the flag, the input renders as a plain interface with all-optional fields.
Subscription operations are generated into services/subscription.service.ts. The generated client accepts the WebSocket endpoint at runtime via ClientConfig.graphqlWsUrl (the framework performs the http→ws / https→wss upgrade on the base URL).
npx @metaengine/graphql-fetch schema.graphql ./src/api --bearer-auth API_TOKENThe generated client.ts exposes an auth config field accepting a token string or an async token provider (() => string | Promise<string>), and adds Authorization: Bearer <token> to every request.
npx @metaengine/graphql-fetch schema.graphql ./src/api --timeout 30Uses AbortSignal.timeout(seconds * 1000) and composes correctly with consumer-supplied AbortSignal.
npx @metaengine/graphql-fetch schema.graphql ./src/api --retries 3Retries retryable responses (429, 503) up to the given number of attempts with exponential backoff.
npx @metaengine/graphql-fetch schema.graphql ./src/api \
--custom-header X-Tenant-ID=TENANT_ID \
--custom-header X-App-Id=APP_IDRepeatable. Each header value comes from a separate env var (baked into the default client).
npx @metaengine/graphql-fetch schema.graphql ./src/api --error-handlingRoutes by HTTP status semantics: 403 / 404 return null; 400 / 409 / 422 return the error body; 401 / 500 / 502 / 503 throw.
npx @metaengine/graphql-fetch schema.graphql ./src/api --result-patternOperations return ApiResult<T> instead of throwing — handle errors as values rather than exceptions:
const result = await user(client, '123');
if (result.ok) {
console.log(result.data?.displayName);
} else {
console.error(result.error.message);
}npx @metaengine/graphql-fetch schema.graphql ./src/api --middlewareGenerated client emits a Middleware interface (onRequest / onResponse / onError) and accepts a middleware array on ClientConfig. Composes with --bearer-auth, --timeout, --retries, and --error-handling.
GraphQL custom scalars resolve to idiomatic TypeScript types. Well-known scalars are mapped out of the box; any other custom scalar defaults to string.
| GraphQL scalar | TypeScript type |
|---|---|
DateTime, Date, Time, DateTimeOffset |
Date |
Decimal, Long, BigInt, ULong, UInt, Short, Byte |
number |
UUID, Guid, Email, URL, URI |
string |
Use --custom-scalar to override the TS type emitted for a scalar. Repeatable. Unsupported targets are hard errors — no silent fallbacks.
| Target | Emitted TS type |
|---|---|
string |
string |
number |
number |
boolean |
boolean |
Date |
Date |
npx @metaengine/graphql-fetch schema.graphql ./src/api \
--custom-scalar DateTime=string \
--custom-scalar UUID=stringInteger-like scalars (
Long,BigInt,ULong) map tonumberby design so values carried in requestvariablessurviveJSON.stringify. Abiginttarget is therefore not offered.
Try the generator with your own schema at https://www.metaengine.eu/converters.
The NuGet package allows programmatic use in .NET projects. See the website documentation for full C# API reference.
- Issues: GitHub Issues
- Email: info@metaengine.eu
- Website: metaengine.eu
MIT License - see LICENSE file for details.
This is the documentation and issue tracking repository for MetaEngine GraphQL Fetch. The compiled NPM package is available at @metaengine/graphql-fetch.
Source code is proprietary, but the package is free to use under MIT license.