NAME

GraphQL::Houtou::PSGI - GraphQL over HTTP endpoint as a plain PSGI app

SYNOPSIS

# app.psgi
use GraphQL::Houtou::PSGI;
use GraphQL::Houtou::DataLoader;

GraphQL::Houtou::PSGI->new(
  schema => $schema,
  graphiql => 1,
  context => sub {
    my ($env) = @_;
    my $users = GraphQL::Houtou::DataLoader->new(batch => \&batch_users);
    my $context = { users => $users, env => $env };
    return ($context, GraphQL::Houtou::DataLoader->on_stall_for($users));
  },
)->to_app;

DESCRIPTION

A GraphQL over HTTP endpoint built directly on the PSGI interface - no Plack modules are required at runtime. Responses are rendered by the direct-JSON execution lane (execute_document_to_json), so the Perl response envelope is never materialized: sync schemas take the streaming fast lane and batching (DataLoader) schemas take the async lane with the JSON tail.

OPTIONS

schema / runtime

Either a GraphQL::Houtou::Schema (a native runtime is built once at construction) or a prebuilt GraphQL::Houtou::Runtime::NativeRuntime.

context

A hashref passed to resolvers as-is, or a coderef called once per request with the PSGI $env. The coderef may return the context alone or a ($context, $on_stall) pair - the natural shape when per-request DataLoaders live inside the context (see SYNOPSIS).

on_stall

A static stall-flush hook (see "Batching resolvers" in GraphQL::Houtou). A per-request hook returned by the context builder takes precedence.

graphiql

Serve the GraphiQL IDE (loaded from the esm.sh CDN) on GET requests whose Accept includes text/html. graphiql_path overrides the endpoint URL the IDE posts to (defaults to the page's own path).

async

Passed to build_native_runtime when the app is constructed from a schema. Declare it when resolvers return promises (DataLoader or other Promise::XS sources) so every request starts on the async-capable lane (see "Declaring an async schema" in GraphQL::Houtou). Requests that carry an on_stall hook run on the async lane either way, so pure DataLoader apps work without it - async matters when promises can appear without a stall-flush hook.

root_value, max_depth, max_nodes, max_cost, default_list_size, program_cache_max

Passed through to the runtime. max_depth caps query nesting; max_nodes caps the total field selections an operation resolves (alias-flooding defense). max_cost caps weighted field cost; default_list_size is the multiplier for list fields without an explicit list_size (defaults to 10). Limits reject over-limit queries with an errors-only 400 response.

allow_introspection

Defaults to true. Set to 0 on public endpoints to reject __schema and __type with an INTROSPECTION_DISABLED request error. __typename remains available. When schema is supplied the option is also applied to the runtime built by the adapter; with a prebuilt runtime it is applied to each document request. GraphiQL relies on schema introspection, so do not combine graphiql => 1 with a disabled policy unless trusted requests override it outside this adapter.

max_body_size

Maximum request body in bytes (default 1048576, i.e. 1 MiB). Bodies whose Content-Length exceeds this, or that read past it, are rejected with a 413 before parsing. Pass 0 to disable the cap.

PROTOCOL

POST with Content-Type: application/json and a {"query": ..., "variables": ..., "operationName": ...} body. The response is application/graphql-response+json when the client accepts it, application/json otherwise. Requests that fail before execution (malformed body, parse or validation errors, unknown operationName) return 400 with an errors-only envelope; field-level errors execute to a 200 response with the errors array populated, as GraphQL over HTTP specifies. GET execution is not implemented; GET serves GraphiQL when enabled and 405 otherwise.

Requests naming an operationName are cached like any other: the program cache keys on the (query, operationName) pair, so clients that always send operationName (Apollo Client and friends) stay on the compiled hot path.