angel_graphql 1.0.0-alpha copy "angel_graphql: ^1.0.0-alpha" to clipboard
angel_graphql: ^1.0.0-alpha copied to clipboard

outdated

The fastest + easiest way to get a GraphQL backend in Dart, using Angel.

Logo


Pub Pub

A complete implementation of the official GraphQL specification - these are the Angel framework-specific bindings.

The goal of this project is to provide to server-side users of Dart an alternative to REST API's. package:angel_graphql, which, when combined with the allows server-side Dart users to build backends with GraphQL and virtually any database imaginable.

Installation #

To install package:angel_graphql, add the following to your pubspec.yaml:

dependencies:
    angel_framework: ^2.0.0-alpha
    angel_graphql: ^1.0.0-alpha

Usage #

Using this package is very similar to GraphQL.js - you define a schema, and then mount graphQLHttp in your router to start serving. This implementation supports GraphQL features like introspection, so you can play around with graphiql as well!

Firstly, define your schema. A GraphQL schema contains an object type that defines all querying operations that can be applied to the backend.

A GraphQL schema may also have a mutation object type, which defines operations that change the backend's state, and optionally a subscription type, which defines real-time interactions (coming soon!).

You can use the convertDartType helper to wrap your existing Model/PODO classes, and make GraphQL aware of them without duplicated effort.

import 'package:angel_framework/angel_framework.dart';
import 'package:angel_graphql/angel_graphql.dart';
import 'package:graphql_schema/graphql_schema.dart';
import 'package:graphql_server/graphql_server.dart';
import 'package:graphql_server/mirrors.dart';

Future configureServer(Angel app) async {
    var queryType = objectType(
        'Query',
        description: 'A simple API that manages your to-do list.',
        fields: [
            field(
                'todos',
                listOf(convertDartType(Todo).nonNullable()),
                resolve: resolveViaServiceIndex(todoService),
            ),
            field(
                'todo',
                convertDartType(Todo),
                resolve: resolveViaServiceRead(todoService),
                inputs: [
                    new GraphQLFieldInput('id', graphQLId.nonNullable()),
                ],
            ),
        ],
    );

    var mutationType = objectType(
        'Mutation',
        description: 'Modify the to-do list.',
        fields: [
            field(
                'create',
                graphQLString,
            ),
        ],
    );

    var schema = graphQLSchema(
        queryType: queryType,
        mutationType: mutationType,
    );
}

After you've created your GraphQLSchema, you just need to wrap in a call to graphQLHttp, a request handler that responds to GraphQL.

In development, it's also highly recommended to mount the graphiQL handler, which serves GraphQL's official visual interface, for easy querying and feedback.

app.all('/graphql', graphQLHttp(new GraphQL(schema)));
app.get('/graphiql', graphiQL());

All that's left now is just to start the server!

var server = await http.startServer('127.0.0.1', 3000);
var uri =
    new Uri(scheme: 'http', host: server.address.address, port: server.port);
var graphiqlUri = uri.replace(path: 'graphiql');
print('Listening at $uri');
print('Access graphiql at $graphiqlUri');

Visit your /graphiql endpoint, and you'll see the graphiql UI, ready-to-go!

Graphiql screenshot

Now you're ready to build a GraphQL API!

Using Services #

What would Angel be without services? For those unfamiliar - in Angel, Service is a base class that implements CRUD functionality, and serves as the database interface within an Angel application. They are well-suited for NoSQL or other databases without a schema (they can be used with SQL, but that's not their primary focus).

package:angel_graphql has functionality to resolve fields by interacting with services.

Consider our previous example, and note the calls to resolveViaServiceIndex and resolveViaServiceRead:

var queryType = objectType(
    'Query',
    description: 'A simple API that manages your to-do list.',
    fields: [
      field(
        'todos',
        listOf(convertDartType(Todo).nonNullable()),
        resolve: resolveViaServiceIndex(todoService),
      ),
      field(
        'todo',
        convertDartType(Todo),
        resolve: resolveViaServiceRead(todoService),
        inputs: [
          new GraphQLFieldInput('id', graphQLId.nonNullable()),
        ],
      ),
    ],
  );

In all, there are:

  • resolveViaServiceIndex
  • resolveViaServiceFindOne
  • resolveViaServiceRead
  • resolveViaServiceModify
  • resolveViaServiceUpdate
  • resolveViaServiceRemove

As one might imagine, using these convenience helpers makes it much quicker to implement CRUD functionality in a GraphQL API.

Documentation #

The convertDartType function can automatically read the documentation from a type like the following:

@GraphQLDocumentation(description: 'Any object with a .text (String) property.')
abstract class HasText {
  String get text;
}

@serializable
@GraphQLDocumentation(
    description: 'A task that might not be completed yet. **Yay! Markdown!**')
class Todo extends Model implements HasText {
  String text;

  @GraphQLDocumentation(deprecationReason: 'Use `completion_status` instead.')
  bool completed;

  CompletionStatus completionStatus;

  Todo({this.text, this.completed, this.completionStatus});
}

@GraphQLDocumentation(description: 'The completion status of a to-do item.')
enum CompletionStatus { COMPLETE, INCOMPLETE }

You can also manually provide documentation for parameters and endpoints, via a description parameter on almost all related functions.

See package:graphql_schema for more documentation.