graphql_openapi_codegen 1.0.0 copy "graphql_openapi_codegen: ^1.0.0" to clipboard
graphql_openapi_codegen: ^1.0.0 copied to clipboard

Generates a Dart server from a GraphQL schema: models, enums, resolver stubs, validators, REST endpoints and an OpenAPI document. Schema-first, for servers not clients.

GraphQL OpenAPI Codegen #

Build Pub Version License

One schema in, a whole backend out #

You have a GraphQL schema and you want a Dart backend from it. Run this once and you have everything you need to start: the models, the enums, the resolver stubs, a REST endpoint for every operation, an OpenAPI 3.0.3 document, and two pages your API serves by itself.

Those two pages are what you notice first. A GraphQL Playground on /graphql-doc, where anyone can browse your schema and run a real query against the live API. A Swagger UI on /rest-doc, over the OpenAPI document the same run produced. Both written, both mounted, both on whatever path you want.

The resolvers content are the only code you write.

type Query {
  product(query: ProductQuery!): Product
}
lib/models/product_type.dart               the model, serialisable, documented
lib/resolvers/query_product_resolver.dart  ← the one file you write
lib/rest/endpoints/query_product_endpoint.dart
lib/rest/rest_routes.dart                  registerRestRoutes(Router)
lib/routes/graphql_doc_route.dart          the GraphQL Playground page
lib/routes/rest_doc_route.dart             the Swagger UI page
lib/routes/doc_routes.dart                 registerDocRoutes(Router)
assets/openapi.yaml

Why this one and not the others #

Everything else on pub.flutter-io.cn in this space generates clients. graphql_codegen, artemis and gql_build take your schema and your queries and give you typed request code to call someone else's API.

This one goes the other way: it builds the API. And it is the only one that hands you an OpenAPI document, so the same schema serves GraphQL clients and REST clients without you writing the REST layer twice.

Use it if you recognise any of these:

  • you maintain a GraphQL server in Dart and write the model classes by hand;
  • you need to expose a REST facade over an existing GraphQL API;
  • someone asked you for an OpenAPI spec and your source of truth is a .graphql file;
  • your schema moves and the boilerplate never keeps up.

The stack underneath #

This generator writes the code. Four packages run it, all on pub.flutter-io.cn and all maintained alongside this one:

graphql_parser3 reads the SDL and your queries into an AST, every node carrying a source span
graphql_schema3 the type system: object, input, union, enum and scalar types
graphql_generator3 a build_runner builder turning the annotated model classes into those types
graphql_server3 the runtime that executes a query, a mutation or a subscription against a schema

They fit together in one direction, and the run walks the whole way:

schema.graphql
  → this generator          writes Product, annotated @graphQLClass
  → graphql_generator3      writes productGraphQLType into product_type.g.dart
  → this generator          writes queryFields, which reference that type
  → you                     GraphQLSchema(queryType: objectType('Query', fields: queryFields))
  → graphql_server3         GraphQL(schema).parseAndExecute(query)

The one line you write by hand is the schema assembly, and it does not change when the schema does: a new query adds an entry to queryFields, which is regenerated. example/bin/server.dart is that line, plus a POST /graphql handler in fourteen lines of shelf.

graphql_server3 IS NOT REQUIRED #

Nothing forces you to serve GraphQL at all. The generated REST endpoints call your resolvers directly and never go through the executor, so leaving graphql_server3 out costs you the API on /graphql and the Playground page, which routes.graphql_doc: '' then turns off. The endpoints, the OpenAPI document and the Swagger page stand on their own.

What it writes #

From the SDL It generates
type a model class with @graphQLClass, @JsonSerializable() and optionally @CopyWith()
input the same, as a GraphQL input class
enum one shared enums.dart
a field with arguments a resolver stub, created once and never overwritten
Query / Mutation / Subscription the field list, plus one REST endpoint each
a custom @_directive a validator stub, created once
the whole schema an OpenAPI 3.0.3 document
the whole schema a GraphQL Playground page and a Swagger UI page, mounted for you

Generated code is emitted lint-clean: sorted package: imports, a library; directive under the header, one trailing newline, no async on a body that never awaits.

Usage #

dart pub add dev:graphql_openapi_codegen
dart run graphql_openapi_codegen

It reads the SDL, writes the sources, runs build_runner for the .g.dart parts, then dart format on what it overwrites.

example/ is a server built this way, small enough to read in one sitting and runnable in three commands. Four files in it carry hand-written logic; the rest came out of a nine-type schema.

Configuration #

Everything is read from the graphql_openapi_codegen: section of your pubspec.yaml, next to the package name the generator has to read anyway. Every entry is optional; the defaults describe a plain package with no versioned layout and no class prefix.

graphql_openapi_codegen:
  schema: lib/schema.graphql     # the SDL every generator reads
  class_prefix: ''               # replaces the leading `_` of a host type
  copy_with: true                # emit @CopyWith() on the models
  api_name: ''                   # OpenAPI title, defaults to the package name
  api_servers: []                # OpenAPI `servers:` entries
  routes:                        # every entry here is an HTTP path
    graphql: /graphql            # where the GraphQL API answers
    rest: ''                     # prefix the REST endpoints are mounted under
    graphql_doc: /graphql-doc    # the Playground page, '' disables it
    rest_doc: /rest-doc          # the Swagger UI page, '' disables it
    openapi: /openapi.yaml       # where Swagger fetches the document
  output:                        # every entry here is a filesystem path
    models: lib/models             # the model, input and enum classes
    fields: lib/graphql/fields     # the Query/Mutation/Subscription field lists
    validators: lib/validators     # one stub per custom `@_directive`
    resolvers: lib/resolvers       # the resolver stubs and register_all.dart
    graphql: lib/graphql           # graphql_resolvers_registry.dart
    rest: lib/rest                 # rest_routes.dart
    endpoints: lib/rest/endpoints  # one handler per operation
    openapi: assets                # where openapi.yaml is written
    routes: lib/routes             # the two doc pages and doc_routes.dart

routes holds HTTP paths, output holds filesystem paths. They share some names on purpose: routes.rest is the prefix your REST endpoints answer on, output.rest is the directory rest_routes.dart lands in.

Setting routes.rest prefixes both the generated registerRestRoutes and the paths: of the OpenAPI document, so Swagger cannot end up calling a path your server does not serve.

The SDL marks a type belonging to the host with a leading underscore, which is not a legal start for a public Dart identifier. class_prefix is what replaces it: _Company becomes Company when empty, BmcCompany when set to Bmc.

What it overwrites, and what it does not #

Regenerated on every run, so never edit them:

  • everything under output.models and output.fields
  • rest_routes.dart, doc_routes.dart, register_all.dart, the validators.dart facade
  • the OpenAPI document

Created once and then yours, so the generator will not touch your work:

  • the resolver stubs
  • the REST endpoint handlers
  • the individual validators
  • the two documentation pages
  • graphql_resolvers_registry.dart

A file in the second list is formatted the run that creates it, and never again: after that it is yours, and reformatting it would rewrite your work.

Requirements #

The generated code imports these, so the package you generate into declares them. graphql_schema3 is the type system the models are annotated for and graphql_generator3 is the builder that reads those annotations; add graphql_server3 when you want to serve GraphQL as well as REST:

dependencies:
  graphql_schema3: ^3.2.1
  graphql_server3: ^3.2.2         # only to serve GraphQL; REST needs none of it
  json_annotation: ^4.12.0
  copy_with_extension: ^17.1.0   # only if copy_with is left on
  shelf: ^1.4.2                  # only if the schema has Query/Mutation fields
  shelf_router: ^1.1.4           # idem, for the generated REST routes

dev_dependencies:
  build_runner: ^2.16.1
  graphql_generator3: ^3.2.1
  json_serializable: ^6.14.1
  copy_with_extension_gen: ^17.1.0   # only if copy_with is left on

Custom directives #

A directive whose name starts with an underscore becomes a validator. Declare it in the SDL, use it on an input field, and the generator scaffolds the function once:

"""Rejects a VAT number that fails the modulo 97 check."""
directive @_vatNo on INPUT_FIELD_DEFINITION

input CompanyInput {
  vatNo: String! @_vatNo
}

You get lib/validators/valid_vat_no.dart with a validVatNo(dynamic value) to fill in. Name the directive after the field it guards, not after the check: valid is prefixed for you, so @_validVat would give you validValidVat.

The model calls it from an assert in its constructor, so it runs under dart run --enable-asserts and is compiled out of a release build. That suits a check restating what the schema already promises. Input you do not trust belongs in the resolver, where a failure can become a 400 instead of a 500.

Limitations #

  • One schema per package.
  • @JsonSerializable(fieldRename:) is not read: honouring it would rename fields in schemas already serving traffic. Use @JsonKey(name:) instead.
  • The documentation pages ship the bare minimum. They apply the CORS headers they are given and nothing else; authentication and branding are yours to add once, in files the generator will not overwrite.
0
likes
0
points
38
downloads

Publisher

verified publishercomapps.be

Weekly Downloads

Generates a Dart server from a GraphQL schema: models, enums, resolver stubs, validators, REST endpoints and an OpenAPI document. Schema-first, for servers not clients.

Homepage
Repository (GitHub)
View/report issues

Topics

#graphql #openapi #codegen #server #rest

License

unknown (license)

Dependencies

collection, gql, path, recase, yaml

More

Packages that depend on graphql_openapi_codegen